Java中的DLC—NIO系列(二):Channel

Java中的DLC—NIO系列(二):Channel

人生苦短,不如养狗

作者:Brucebat.Sun

一、基本概念

  在上一个章节中我们简单了解到Channel在NIO中的角色和Stream在IO中的角色有些类似,但是特性上却不尽相同。如果说Stream就像生活中的水流一样,那么Channel就如同交通中能够双向行驶的隧道。前者的数据流向固定,也就意味着同一个流只能完成读取或者写入其中一种操作,而后者则能够同时进行读取和写入两种操作。同时在数据传输过程中Channel也对Stream进行了进一步的改进处理,Channel中是通过buffer(也即缓冲区)来进行数据的装载和传输,而Stream中则直接以字节为单位进行数据的传输操作。

  结合上面的描述,我们来对Channel和Stream的特性进行一个简单的对比:


Stream Channel
是否支持异步
数据传输方式 单工,仅支持读取或写入 全双工,支持同时进行读取和写入操作
是否依赖buffer 否,即按照逐个字节传输 是,即按照逐个buffer传输

  从上面的对比我们不难发现,相比传统IO,NIO在设计思路上更接近于底层操作系统的I/O实现方式,即使用缓冲区的方式来进行数据的批量装载和分组传输,这也是NIO在传输速度上优于传统IO的原因之一。但这种方式也意味着在实际使用Channel时,我们必须要依赖Buffer类来完成对于数据的读写,即如下展示:

读数据:
 channel -> buffer -> program
写数据:
 program -> buffer -> channel

二、基本使用

  点开JDK提供的NIO包我们会看到一长串的Channel类列表,其中最为重要和常见的子类如下:

Java中的DLC—NIO系列(二):Channel

  从上图中我们不难发现NIO提供了基于处理的数据不同分为两类Channel,一类是针对文件IO的Channel,一类是针对网络IO的Channel。需要注意的是,针对文件IO的Channel不能被设置成阻塞模式,同样也不支持多路复用模式,这一特性也符合操作系统对于纯文件类型数据的IO处理。下面笔者将会基于FileChannel来简单说明一下针对文件IO的Channel的使用。

FileChannel

  NIO类库为我们提供了如下三种方式来获取FileChannel对象:

  • 通过FileChannel自身提供了open方法获取:通过这种方式可以明确指定打开通道的特定操作选项,如标准打开选项、处理符号链接的选项等。示例如下:

    FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ);
  • 通过FileInputStream/FileOutputStream获取:通过这种方式获取到的通道只能以读或者写模式打开文件,对应读写模式是继承自对应的Stream对象。示例如下:

    FileInputStream inputStream = new FileInputStream(new File("test.txt"));
    // 获取一个只读的文件通道
    FileChannel inChannel = inputStream.getChannel();

    // ============= FileInputStream.getChannel() ======
    public FileChannel getChannel() {
            synchronized (this) {
                if (channel == null) {
                  // 这里如果初始化FileInputStream时没有设置channel则使用open方法初始化一个只读通道
                  channel = FileChannelImpl.open(fd, path, truefalsethis);
                }
                return channel;
            }
    }

  • 通过RandomAccessFile获取:通过这种方式获取的通道会继承RandomAccessFile对象设置的读写模式。示例如下:

    RandomAccessFile file = new RandomAccessFile("test.txt""rw");
    // 获取一个可读可写的文件通道
    FileChannel channel = file.getChannel();

    // ============= RandomAccessFile.getChannel() ======
    public final FileChannel getChannel() {
            synchronized (this) {
                if (channel == null) {
                  // 区别于文件流这里在使用open方法初始化通道时默认是可读的,即当前文件处于只写模式时使用getChannel()获取到的通道也是可读可写的
                  channel = FileChannelImpl.open(fd, path, true, rw, this);
                }
                return channel;
            }
    }

  在获取FileChannel对象之后,可以根据FileChannel提供的read()或者write()方法对文件进行读取或者写入操作。需要注意的是,上文提到过在实际使用读写方法时需要依赖缓冲区来完成对应,下面我们来分别看一下读数据和写数据的例子:

1. 读数据

FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(5);
while(channel.read(buf)!=-1){
    buf.flip();
    System.out.print(new String(buf.array()));
    buf.clear();
}
channel.close();

2. 写数据

FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.WRITE);
ByteBuffer buf = ByteBuffer.allocate(20);
byte[] data = "Hello, world!".getBytes();
for (int i = 0; i < data.length; ) {
    buf.put(data, i, Math.min(data.length - i, buf.limit() - buf.position()));
    buf.flip();
    i += channel.write(buf);
    buf.compact();
}
channel.force(false);
channel.close();

  需要注意,在上面的实例代码中使用的都是FileChannel原生提供的获取方法,且在进行选项设置的时候只使用了读或者写模式,所以在进行文件的读写操作时都是从文件的头部开始的。当然具体的开始位置还取决于Buffer对象中初始position的设置。

总结

  以上就是Channel类基本概念和针对文件IO的FileChannel基本使用的介绍,在后续的章节中我们会针对网络IO相关的Channel使用进行具体的学习和介绍。

  疫情还在继续,希望大家注意防护,身体健康,保持每天好心情~~


原文始发于微信公众号(Brucebat的伪技术鱼塘):Java中的DLC—NIO系列(二):Channel

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/90025.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!