NIO的一些知识点

NIO的一些知识点

薛定谔的汪

学习 NIO有一段时间了,今天主要总结下什么是 NIO,以及 NIO 的三大组件Buffer、Channel、Selector以及其他相关概念。

NIO

传统 BIO 和 NIO最大的区别在于 NIO 是非阻塞的,阻塞和非阻塞的区别在于线程得到返回结果前是否可以做其他事情,阻塞是不可以的,非阻塞可以。

举个例子:一个餐厅里客人来了后要有服务员对客人进行服务,BIO是每来一个客人都得有一个服务员服务他,在客人吃完饭之前,服务员不能做其他的事直至客人吃完饭走人。但是 NIO 不一样,NIO 可以让同一个服务员同时服务多个客人,在客人 A 点餐时服务员可以去接待客人 B。这种模式大大节省了成本同时提高了效率。

NIO是基于通道、缓冲区、多路复用进行操作,数据是从通道读取到缓冲区,从缓冲区写入到通道。

Buffer


Buffer 是 NIO中三大组件之一,本质上就是内存中的一块区域,这块区域称作缓冲区。

Java NIO 中 Buffer 的继承关系图:

最常使用的是 ByteBuffer,Buffer 可以理解为一个数组,ByteBuffer 里面就是 Byte 数组,只不过除了数组以外还有其他的属性。

capacity

capacity属性表示 Buffer 的容量,我们知道数组一旦初始化后,容量是不可更改的。capacity 就是初始化 Buffer 的时候数组的容量。

position

因为 NIO 的 Buffer 是双工的,可读可写,所以 Buffer 分为读模式和写模式,当我们初始化一个 Buffer 后,一开始是写模式,通过调用 Buffer的flip()方法切换读模式(flip()方法就是将 Buffer 的写模式切换到读模式)。

position初始为0,在写模式下,每次向 Buffer 中写入一个值,position 的值+1,读模式也一样,每读一个值 position+1。

调用flip()方法,position 值清零,这样就是从头开始读了。

limit

limit 属性在读写模式下也有不同的含义:

写模式:limit 代表最大能写入的数据量,此时就等于 capacity

读模式:此时的 limit等于 Buffer 中实际的数据量。

mark

mark 用于临时标记 position 的值,调用 mark()方法会将 mark 设置为当前的position。

用途:当我们读Buffer 读到某个位置时,mark 标记一下,继续往下读了一会,想再回到刚才那个标记的位置时,只要调用下reset()方法即可。

注意:这里的读写模式是相对与 Buffer 的,但是从系统层面上来讲,数据从外部 Channel 到内存 Buffer里是系统意义上的读,对应的是 Buffer 的写,数据从内存 Buffer 到 Channel 是系统意义上的写,对应的是 Buffer 的读,这里要区分清楚。

Channel


NIO 操作都需要通道 Channel,Channel 是 jdk1.4引入 NIO 之后才有的概念,类似于传统 IO 中的流,它是数据来源或者数据写入的目的地,与 Buffer 进行交互。

Java NIO 中的 Channel:

FileChannle:文件通道

SocketChannel:TCP连接通道,数据传输时使用。

ServerSocketChannel:服务端 TCP 通道,监听端口进来的请求

Channel称作通道,系统可以将数据从 Channel 读到 Buffer 中(channel.read(buffer)),也可以从 Buffer 中写入到 Channel(channel.write(buffer))

Selector


我们知道 NIO 是非阻塞的,非阻塞的意义就是线程在等待某个操作的结果返回之前可以去干其他事。Selector 就是建立在非阻塞的基础上,其作用是在一个线程中管理多个 Channel,每个 Channel 做各自的事。

Buffer、Channel、Selector 之间的关系图:

分散读取和聚集写入


上图看到一个Channel 可以对应多个 Buffer,这涉及到了 NIO 中一个很重要的概念:分散读取和聚集写入

分散读取

一个 Channel 可以对应多个 Buffer,如当我们读取一个文件时,可以使用多个缓冲区来读这样会大大提高读取效率,将缓冲区的内容写入到Channel中时会用到聚集写入。

聚集写入

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) throws Exception {
//可读可写文件
RandomAccessFile file1 = new RandomAccessFile("test1.txt","rw");
//获取 channel
FileChannel channel1 = file1.getChannel();
//指定缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//构造多缓冲区数组
ByteBuffer[] bufs = {buf1,buf2};
//Channel1分散读取数据到多个Buffer中,对应 Buffer是写模式
channel1.read(bufs);
//输出读取的内容:
System.out.println(new String(bufs[0].array()).trim());
System.out.println(new String(bufs[1].array()).trim());

//下面开始聚集写入,将 两个Buffer中的数据聚集写入到Channel2,再到 text2.txt 文件中
RandomAccessFile file2 = new RandomAccessFile("test2.txt","rw");
FileChannel channel2 = file2.getChannel();
System.out.println("=======开始聚集写入到 text2.txt 文件中======");
for (int i = 0; i < bufs.length; i++) {
//聚集写入时,应切换 Buffer 为读模式
bufs[i].flip();
}
channel2.write(bufs);

//关闭
file2.close();
file1.close();
}

非直接缓冲区和直接缓冲区

缓冲区分为直接缓冲区和非直接缓冲区,这俩是什么?有什么区别呢?

非直接缓冲区

非直接缓冲区是在JVM中,初始化 Buffer 时调用allocate()方法,此方式使用的是非直接缓冲区。

直接缓冲区

直接缓冲区直接在内存中,比如当我们从磁盘读取数据时,直接读取到物理内存里,这块内存叫直接缓冲区。

通过allocateDirect()方法创建。

二者区别

直接缓冲区相比非直接缓冲区效率更高,因为相较于非直接缓存区省去了从内存拷贝到 JVM 中的操作,但是非直接缓冲区的好处是缓冲区交给 JVM 管理更安全且便于回收。

  • Title: NIO的一些知识点
  • Author: 薛定谔的汪
  • Created at : 2018-09-03 18:01:54
  • Updated at : 2023-11-17 19:37:37
  • Link: https://www.zhengyk.cn/2018/09/03/netty/nio-theory/
  • License: This work is licensed under CC BY-NC-SA 4.0.