详解,NIO中的通道(Channel)

简介: 这是 IO 相关的第三篇通道,主要讲解一下通道是什么,在 Java NIO 中的体系及使用。能被称为 NIO 中的三大组件之一作用肯定是不言而喻的,所以对于通道的掌握还是很重要的,那我们往下看把!


一、什么是通道


百度词贴:


image.png


从百度解释来看:通道主要用来传输数据的一条道路。


而在 NIO 中,通道的作用也是如此:传输数据,将“原缓冲区”与“目标缓冲区”要交换的数据进行传输


很明显通道是作用与缓冲区的,所以读了上篇本人写得《详解,NIO中的缓冲区》那么我们得出下面这张图:


image.png


二、NIO中的通道体系


在 IDEA 中我观察 Channel 的类继承关系时,发现好复杂呀,我点进源码看它的继承接口和实现类,发现超复杂,最后还是放弃通过 IDEA 看了。


所以我去看了 JDK8 的 API 文档找出了 Channel 的相关信息,如下。


1、父接口


AutoCloseable:自动关闭流,而不需要显式地调用 close ()方法。

Closeable:关闭 IO 流,释放系统资源。


2、直接子接口


  • AsynchronousByteChannel:支持异步 IO 操作,单位时字节。
  • AsynchronousChannel:支持异步 IO 操作。
  • ByteChannel:继承 ReadableByteChannel 和 WritableByteChannel 接口允许对baty进行读写操作。
  • GatheringByteChannel:使接口可以将多个缓冲区中的数据写入通道。
  • InterruptibleChannel:使通道能以异步的方式进行关闭与中断。
  • MulticastChannel:使通道支持一个多播的功能,可以理解同时向多个主机发送数据。
  • NetworkChannel:主要作用是使通道与 Socket 进行关联,是通道中的数据能在 Socket 技术上进行传输。
  • ReadableByteChannel:是通道允许对字节进行读操作。
  • ScatteringByteChannel:主要作用时可以从通道中读取字节到多个缓冲区中。
  • SeekableByteChannel:主要作用是在字节通道中维护 position ,以及允许 position 发生改变。
  • WritableByteChannel:使通道允许对字节进行写操作。


3、所有已知实现类


  • AbstractInterruptibleChannel:提供一个可以被中断的通道基本实现。
  • AbstractSelectableChannel:可选通道的基本实现,该类定义了处理通道注册、注销和关闭机制的方法。
  • AsynchronousFileChannel:可以以异步的方式从文件读取或往文件写入数据。
  • AsynchronousServerSocketChannel:用于面向流的服务端的异步通道。
  • AsynchronousSocketChannel:用于面向流的客户端的异步通道。
  • DatagramChannel:面向无连接的套接字的可选通道。
  • FileChannel:继承 AbstractInterruptibleChannel 类,主要作用时读取、写入、映射和操作文件的通道。该通道永远是阻塞的操作。
  • Pipe.SinkChannel:一个代表Pipe的可写端的通道。
  • Pipe.SourceChannel:一个代表Pipe的可读端的通道。
  • SelectableChannel:可通过Selector复用的通道。
  • ServerSocketChannel:面向连接的服务端通道。
  • SocketChannel:面向连接的客户端通道。
  • Channel 体系确实很庞大,所以我们不需要全部的去深入它们,只需要知道其中的几个就行,比如:FileChannel、ServerSocketChannel、SocketChannel等。


那下面就先看看 FileChannel 也是用的最多的一个其它的下次介绍。


三、FileChannel 类使用


先看类结构图:


image.png


再看 API 图:


image.png


一、获取文件通道及读取操作

方式一:

FileChannel fileChannel = FileChannel.open(new File("src/channel/j3.txt").toPath(), StandardOpenOption.WRITE, Standard


解释:根据一个指定的文件获取一个可读写的文件通道

StandardOpenOption 枚举可以指定通道的读写权限。


public enum StandardOpenOption implements OpenOption {
    READ, // 读
    WRITE, // 写
    APPEND, // 在写模式下,进行追加写
    TRUNCATE_EXISTING, // 如果文件已经存在,并且它被打开以进行WRITE访问,那么它的长度将被截断为0。如果文件仅以READ访问方式打开,则忽略此选项。
    CREATE, // 如果文件不存在,请创建一个新文件。如果还设置了CREATE_NEW选项,则忽略此选项。与其他文件系统操作相比,检查文件是否存在以及创建文件(如果不存在)是原子性的。
    CREATE_NEW, // 创建一个新文件,如果文件已经存在则失败。与其他文件系统操作相比,检查文件是否存在以及创建文件(如果不存在)是原子性的。
    DELETE_ON_CLOSE, // 关闭时删除文件
    SPARSE, // 稀疏文件。当与CREATE_NEW选项一起使用时,此选项将提示新文件将是稀疏的。当文件系统不支持创建稀疏文件时,该选项将被忽略。
    SYNC, // 要求对文件内容或元数据的每次更新都以同步方式写入底层存储设备。
    DSYNC; // 要求对文件内容的每次更新都以同步方式写入底层存储设备。
}


案例:

@Test
public void channelTest() throws IOException {
    // 获得一个根据指定文件路径的读写权限文件通道
    FileChannel fileChannel = FileChannel.open(new File("src/channel/j3.txt").toPath(), StandardOpenOption.WRITE, StandardOpenOption.READ);
    // 获得一段有指定内容的缓冲区
    ByteBuffer source = ByteBuffer.wrap("HelloWorld,J3-baiqi".getBytes(StandardCharsets.UTF_8));
    // 空的缓冲区
    ByteBuffer target = ByteBuffer.allocate(50);
    log.info("fileChannel.position():{}", fileChannel.position());
    // 将缓冲区中的内容写入文件通道
    fileChannel.write(source);
    // 通道大小
    log.info("fileChannel.position():{}", fileChannel.position());
    // 设置读写位置
    fileChannel.position(0);
    // 将通道中的内容写到空缓冲区
    fileChannel.read(target);
    // 转换缓冲区读写模式
    target.flip();
    log.info("target:,{}", new String(target.array(), 0, target.limit()));
    //关闭资源
    fileChannel.close();
}


方式二:

FileInputStream fileInputStream = new FileInputStream("src/channel/j3.txt");
FileChannel channel = fileInputStream.getChannel();


解释:根据一个文件流获取对应的文件通道,通道的读写权限由流的输入输出决定。

输入 ==》读

输出 ==》写


案例:

@Test
public void channelTest02() throws IOException {
    // 获取输出流
    FileOutputStream outputStream = new FileOutputStream("src/channel/j3.txt");
    // 根据输出流获得一个 “写” 权限的通道
    FileChannel outChannel = outputStream.getChannel();
    // 获得一个有指定内容的缓冲区
    ByteBuffer source = ByteBuffer.wrap("HelloWorld,J3-baiqi".getBytes(StandardCharsets.UTF_8));
    // 将缓冲区内容写入到通道
    outChannel.write(source);
    // ===============================================================
    // 获取输入流
    FileInputStream fileInputStream = new FileInputStream("src/channel/j3.txt");
    // 根据输入流获得一个 “读” 权限的通道
    FileChannel inChannel = fileInputStream.getChannel();
    // 获得一个空内容的缓冲区
    ByteBuffer target = ByteBuffer.allocate(50);
    // 将通道中的内容读到缓冲区
    inChannel.read(target);
    // 转换缓冲区读写模式
    target.flip();
    // 读出缓冲区中的内容
    log.info("target:,{}", new String(target.array(), 0, target.limit()));
    //关闭资源
    outChannel.close();
    inChannel.close();
}


上面介绍的两个案例实现了通道的基本操作获取、读、写。如果细心的人可以发现一点非常别扭的地方就是通道的读与写的理解,write 是写操作,但被 Channel 调用后就变成了将数据写入通道有点读取数据的意思,反之亦然。


对于这种别扭的地方,我们要如何区理解呢!我说说我的理解:


通道由空变成非空就是写,缓冲区向通道写入了数据;


通道由非空变成空就是读,缓冲区从通道读取了数据。


二、文件复制操作


下面介绍 FileChannel 中两个文件复制操作 API ,非常方便好用,在实际项目中也是有运用。


transferTo:将数据复制到目标对象中。

transferFrom:将数据从目标对象中复制给自己。

这两个 API 的作用一样,就是作用对象不一样,如果调用方是有数据的那就用 transferTo,反之则用 transferFrom 。


案例:

@Test
public void copyTest() throws IOException {
    /*
        需求:将一个视频文件从F:\\Channel\\a.mp4复制到F:\\Channel\\b.mp4
         */
    // 准备输入流(源文件)
    FileInputStream fileInputStream = new FileInputStream("F:\\Channel\\a.mp4");
    // 准备输出流(目标文件)
    FileOutputStream fileOutputStream = new FileOutputStream("F:\\Channel\\b.mp4");
    // 根据流获取通道
    FileChannel inputStreamChannel = fileInputStream.getChannel();
    FileChannel outputStreamChannel = fileOutputStream.getChannel();
    // 指向复制方法
    // outputStreamChannel.transferFrom(inputStreamChannel, 0, inputStreamChannel.size());
    inputStreamChannel.transferTo(0, inputStreamChannel.size(), outputStreamChannel);
    // 关闭资源
    fileInputStream.close();
    fileOutputStream.close();
}


再来对比一下原生 BIO 复制文件操作:

@Test
public void copyTest2() throws IOException {
    /*
        需求:将一个视频文件从F:\\Channel\\a.mp4复制到F:\\Channel\\b.mp4
         */
    // 准备输入流(源文件)
    FileInputStream fileInputStream = new FileInputStream("F:\\Channel\\a.mp4");
    // 准备输出流(目标文件)
    FileOutputStream fileOutputStream = new FileOutputStream("F:\\Channel\\b.mp4");
    //存储数据的字节数组
    byte[] b = new byte[1024];
    while (true) {
        //从输入流中读取数据到字节数组中
        int res = fileInputStream.read(b);
        //判断是否读到文件末尾,是就跳出循环
        if (res == -1) {
            break;
        }
        //将字节数组中的数据通过输出流,写到目标文件中
        fileOutputStream.write(b, 0, res);
    }
    fileInputStream.close();
    fileOutputStream.close();
}


两者对比一下,是不是 NIO 的方式较为简单。


不过通过案例对比虽然 NIO FileChannel 方式操作较为简单,但是,在效率上并不比 InputStream 或 OutputStream 高很多,这是因为 NIO 的出现最主要的就是解决阻塞问题,通过 NIO 把线程变成非阻塞这样就提高效率。


而 NIO 的非阻塞与 Socket 相关的通道有关即网络 IO,这些后面会说,在这里只是提一嘴。


四、最后


通篇下来,难点基本上是没有的,主要就是理解 Channel 的作用:传输数据的通道。


然后就简单介绍了通道中用的比较多的文件通道(FileChannel)的基本使用,对于操作文件使用它还是比传统的方式简单的,至少在 API 方面是有体现,而我也做了相关案例。


那这就是 Channel 相关的内容了,虽然本片讲的简单了点,但毕竟不是专业 API 讲解,有时间还是建议看看通道中其他方法的使用。


好了,今天的内容到这里就结束了,关注我,我们下期见


目录
相关文章
|
6月前
|
存储 编解码 移动开发
技术笔记:NIO流—理解Buffer、Channel概念和NIO的读写操作
技术笔记:NIO流—理解Buffer、Channel概念和NIO的读写操作
44 1
|
7月前
|
缓存 网络协议 Java
📌 Java NIO Channel
Java NIOChannel和传统的流相似,但是也存在一些差异: • 在同一个Channel通道中,既可以进行 读操作 也可以进行 写操作,但是 流 只能进行 读 或者 写 其中一种操作。 • Channel通道可以进行异步读写。 • Channel可以从 Buffer中进行读写操作。将数据从Channel通道读取到Buffer缓冲区,并将数据从Buffer缓冲区写入Channel通道。
|
7月前
|
存储 网络协议 Java
NIO - 基础入门之通道和缓冲区
NIO - 基础入门之通道和缓冲区
92 0
|
存储 网络协议 Java
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。
166 0
|
弹性计算 Java API
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
理解Selector 和 Channel Selector 选择器,也叫多路复用器,可以同时处理多个客户端连接,多路复用器采用轮询机制来选择有读写事件的客户端链接进行处理。 通过 Selector ,一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 由于它的读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
263 0
|
网络协议 前端开发 Java
Netty异步NIO框架(一)java服务端与客户端实现聊天 websocket通道
Netty异步NIO框架(一)java服务端与客户端实现聊天 websocket通道
|
缓存 网络协议 Java
Java NIO学习(二):Channel通道
Java NIO 的通道类似流,但又有些不同:
183 0
Java NIO学习(二):Channel通道
|
Java API
NIO学习三-Channel
在学习NIO时,ByteBuffer、Channel、Selector三个组件是必须了解的。前面我们说到ByteBuffer是作为缓冲区进行数据的存放或者获取。通常我们需要进行flip翻转操作,但是这个在Netty中,有一个更为强大的类可以替代ByteBuf,其不需要进行翻转,也可以进行读写的双向操作。要将数据打包到缓冲区中,通常需要使用通道,而通道作为传输数据的载体,也即它可以使数据从一端到另一端,因此就必须进行了解。 Channel中,我们也看到其子类有很多,通常都是用于读写操作的。其中ByteChannel可以进行读写操作,也即可以进行双向操作。 操作过程:首先创建流对象,有了流对象获取
92 0
NIO学习三-Channel
java Nio (四) :通道(Channel)
java Nio (四) :通道(Channel)
|
网络协议 Java
腾讯四面:说说你对Java NIO的通道Channel的理解
- Channel是双向的,我们既可以向Channel中写数据,也可以从Channel中读取数据。但流的读写通常是单向的。
161 0