Skip to content

NIO简介

NIO 中的 N 可以理解为 Non-blocking,不单纯是 New,是解决高并发、I/O高性能的有效方式。

Java NIO是Java1.4之后推出来的一套IO接口,NIO提供了一种完全不同的操作方式, NIO支持面向缓冲区的、基于通道的IO操作。

新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。

NIO VS BIO

BIO

BIO全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型。

Java BIO:服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如下图所示:

        ----------
        - server -
        ----------
            |
            *
    --------------------    
    |                  |
    *                  *
    ----------    ----------
    - thread -    - thread -
    ----------    ----------  
     read/write    read/write
     client1        client1
     ---------------------
     -      BIO 模型      -
     ---------------------

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。

NIO

Java NIO: 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。

        ----------
        - server -
        ----------
            |
            *
        ----------    
        - thread -   
        ----------   
            |
            *
        ----------    
   ------ Selector -------   
   |     ----------     |
   |                    |
   *                    *                     
   read/write    read/write
   client1        client1
   
     ---------------------
     -      BIO 模型      -
     ---------------------

一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理,NIO擅长1个线程管理多条连接,节约系统资源。

NIO的核心实现

NIO 包含3个核心的组件:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)
        ----------
        - server -
        ----------
            |
            *
        ----------    
        - thread -   
        ----------   
            |
            *
        ----------    
   ------ Selector -------   
   |     ----------     |
   |                    |
   *                    *                     
   Channel           Channel
   |                    |
   *                    *
   Buffer             Buffer
   |                    |
   *                    *
   Client             Client

关系图的说明:

  1. 每个 Channel 对应一个 Buffer。
  2. Selector 对应一个线程,一个线程对应多个 Channel。
  3. 该图反应了有三个 Channel 注册到该 Selector。
  4. 程序切换到那个 Channel 是由事件决定的(Event)。
  5. Selector 会根据不同的事件,在各个通道上切换。
  6. Buffer 就是一个内存块,底层是有一个数组。
  7. 数据的读取和写入是通过 Buffer,但是需要flip()切换读写模式,而 BIO 是单向的,要么输入流要么输出流。

Channel(通道)

Channel 是 NIO 的核心概念,它表示一个打开的连接,这个连接可以连接到 I/O 设备(例如:磁盘文件,Socket)或者一个支持 I/O 访问的应用程序,Java NIO 使用缓冲区和通道来进行数据传输。

                                      Channel(通道)
      ----------                                                ------------            
      -        -    ----- 通道--->  ----------  -----通道---->   -          -
      - IO设备  -                   -  缓冲区 -                  -  java应用 -
      -        -   <----- 通道----  ----------  <----通道-----   -          -
      ----------                                                ------------

通道的主要实现类:

FileChannel类

本地文件IO通道,用于读取、写入、映射和操作文件的通道,使用文件通道操作文件的一般流程为:

1)获取通道

文件通道通过 FileChannel 的静态方法 open() 来获取,获取时需要指定文件路径和文件打开方式。

java
// 获取文件通道
FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
2)创建字节缓冲区

文件相关的字节缓冲区有两种,一种是基于堆的 HeapByteBuffer,另一种是基于文件映射,放在堆外内存中的 MappedByteBuffer。

java
// 分配字节缓存
ByteBuffer buf = ByteBuffer.allocate(10);
3)读写操作

读取数据

一般需要一个循环结构来读取数据,读取数据时需要注意切换 ByteBuffer 的读写模式。

java
while (channel.read(buf) != -1){ // 读取通道中的数据,并写入到 buf 中
    buf.flip(); // 缓存区切换到读模式
    while (buf.position() < buf.limit()){ // 读取 buf 中的数据
        text.append((char)buf.get());
    }
    buf.clear(); // 清空 buffer,缓存区切换到写模式
}
写入数据
java
for (int i = 0; i < text.length(); i++) {
    buf.put((byte)text.charAt(i)); // 填充缓冲区,需要将 2 字节的 char 强转为 1 自己的 byte
    if (buf.position() == buf.limit() || i == text.length() - 1) { // 缓存区已满或者已经遍历到最后一个字符
    buf.flip(); // 将缓冲区由写模式置为读模式
    channel.write(buf); // 将缓冲区的数据写到通道
    buf.clear(); // 清空缓存区,将缓冲区置为写模式,下次才能使用
    }
    }
4)将数据刷出到物理磁盘,FileChannel 的 force(boolean metaData) 方法可以确保对文件的操作能够更新到磁盘。
java
channel.force(false);
5)关闭通道
java
channel.close();
SocketChannel类

网络套接字IO通道,TCP协议,针对面向流的连接套接字的可选择通道(一般用在客户端)。

TCP 客户端使用 SocketChannel 与服务端进行交互的流程为:

1)打开通道,连接到服务端。
java
SocketChannel channel = SocketChannel.open(); // 打开通道,此时还没有打开 TCP 连接
channel.connect(new InetSocketAddress("localhost", 9090)); // 连接到服务端
2)分配缓冲区
java
ByteBuffer buf = ByteBuffer.allocate(10); // 分配一个 10 字节的缓冲区,不实用,容量太小
3)配置是否为阻塞方式。(默认为阻塞方式)
java
channel.configureBlocking(false); // 配置通道为非阻塞模式
4)与服务端进行数据交互
java
channel.close();          // 关闭通道

ServerSocketChannel类

网络通信IO操作,TCP协议,针对面向流的监听套接字的可选择通道(一般用于服务端),流程如下:

1)打开一个 ServerSocketChannel 通道, 绑定端口。
java
ServerSocketChannel server = ServerSocketChannel.open(); // 打开通道
2)绑定端口
java
server.bind(new InetSocketAddress(9090)); // 绑定端口
3)阻塞等待连接到来,有新连接时会创建一个 SocketChannel 通道,服务端可以通过这个通道与连接过来的客户端进行通信。等待连接到来的代码一般放在一个循环结构中。
java
SocketChannel client = server.accept(); // 阻塞,直到有连接过来
4)通过 SocketChannel 与客户端进行数据交互
5)关闭 SocketChannel
java
client.close();

Buffer(缓冲区)

缓冲区 Buffer 是 Java NIO 中一个核心概念,在NIO库中,所有数据都是用缓冲区处理的。

在读取数据时,它是直接读到缓冲区中的,在写入数据时,它也是写入到缓冲区中的,任何时候访问 NIO 中的数据,都是将它放到缓冲区中。

而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。

          ----------                 Buffer缓冲区                ------------            
      -        -    ----- 通道--->  ----------  -----通道---->   -          -
      - IO设备  -                   -  缓冲区 -                  -  java应用 -
      -        -   <----- 通道----  ----------  <----通道-----   -          -
      ----------                                                ------------
Buffer 数据类型

Last updated:

版权声明