本文为原创文章,转载请注明出处
查看[Java]系列内容请点击:https://www.jianshu.com/nb/45938443
Java NIO就是Java New IO的缩写,关于老的Java IO的进阶说明请移步:【Java高级】Java IO进阶
Java NIO主要依靠三个类:Channel
、Buffer
和Selector
实现了一系列高效的异步阻塞IO模型。与同步IO相比,其具有高效、简单和异步的特点,使得程序可以在一个线程内管理多个输入、输出。
他们之间的关系可以描述为:
Buffer
是Channel
的底层存储结构,是按照字节、数字等存储的Channel
是一种通道,是对于文件的一种抽象,它使得我们可以访问诸如内存映射、文件加锁机制和文件间数据的快速传递等操作系统特性Selector
轮询Channel
被注册的的事件,一旦Channel
有事件通知,就处理发生在Channel
上的事件
Buffer底层
Buffer
是一个抽象类,实现了它的类包括ByteBuffer
IntBuffer
CharBuffer
等,分别以byte
int
char
等类型的数组作为存储单元。在NIO中,数据只能写到Buffer
中,也读取的数据也只能存储在Buffer
中。
Buffer
的底层使用数组来进行存储,除了数组以外,使用三个指针来控制其读写内容:
- capacity: 容量,就是数组的大小
- position: 初始为0,下一次读写的位置指针(数组下标)
- limit: 读写的极限位置,可被读写的数据区数组下标(比如数据大小为20,其中存储了10条数据,那么在读的时候limit=9)
此外,还有一个mark
字段,与输入流类似,可以在当前位置打标签,然后通过reset
将读取位置position
设置到mark,即可实现重复数据读取。
在进行Buffer
的使用之前需要先申请数组的大小,使用allocate
方法来申请缓冲区数据大小。然后通过对这个数组的反复读写来实现数据的缓存功能
Buffer
常用的一些方法如下:
public Buffer flip()
:读写模式转换,从读模式转换到写模式或者从写模式转换到读模式public Buffer mark()
: 在当前位置打标记public Buffer reset()
: 将position
设置成mark
位置public Buffer clear()
: 清空数据(实际是指针初始化为初始值)public Buffer rewind()
:position
复位到0,可实现数据重复读取public T compact()
: 整理,将数据移动到从数据头开始
Channel
Channel
是一种文件通道,通过Channel
可以获取文件的各种属性等,它与上篇中介绍的流库很类似,但是Channel
只能通过与Buffer
交互来进行数据访问。同时,通道是双向的、可异步读写的。操作文件的是FileChannel
。
下面代码演示了FileChannel
与ByteBuffer
交互处理数据:
public class Test {
public static void main(String[] args) throws Exception {
writeData();
readData();
}
// 写数据
public static void writeData() throws Exception {
FileChannel channel = FileChannel.open(Path.of("D:\\data.txt"), StandardOpenOption.WRITE); // 写模式
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.put("this is a test buffer.".getBytes());
buffer.flip(); // 将buffer变为读模式,方便channel读
channel.write(buffer); // 从buffer读数据
channel.close();
}
// 读数据
public static void readData() throws Exception {
FileChannel channel = FileChannel.open(Path.of("D:\\data.txt"), StandardOpenOption.READ); // 读模式
ByteBuffer buffer = ByteBuffer.allocate(100);
int readSize = -1;
do {
readSize = channel.read(buffer); // 从channel读到buffer,这里buffer是写模式
System.out.println("\nread size: " + readSize);
buffer.flip(); // 转换为读模式
// 取buffer中的数据
while (buffer.hasRemaining()) System.out.print("/" + (char) buffer.get());
buffer.clear();
} while (readSize != -1);
channel.close();
}
}
输出结果:
read size: 22
/t/h/i/s/ /i/s/ /a/ /t/e/s/t/ /b/u/f/f/e/r/.
read size: -1
Selector
Selector
通过【注册-轮询】的机制来使得程序允许以异步的方式获取输入输出。一个Selector
中可以注册多个Channel
,然后Selector
就不断轮询这些Channel
,一旦事件发生,在Selector
里面就可以手动获取到。
首先,创建一个Selector
:
Selector selector = Selector.open();
其次,将Channel
设置为非阻塞模式:
channel.configureBlocking(false);
注意的是,只有继承自SelectableChannel
或者AbstractSelectableChannel
的类才可以设置为非阻塞模式,才能使用Selector
,我们上面使用的FileChannel
不可以使用Selector
,而SocketChannel
SocketServerChannel
等可以使用Selector
。
接着,可以为Selector
注册感兴趣的事件:
SelectionKey key = channel.register(selector, SelectionKey.OP_WRITE);
第一个参数表示要注册的选择器,第二个参数表示感兴趣的事件,事件有多种。
SelectionKey
中有一系列的方法来表示该通道是否就绪,是否可读、可写等,在实际使用过程中,也是使用SelectionKey
来查询通道的状态。
AIO
AIO是一种除了注册事件以外,还注册了事件处理器的模型,可以初步理解为使用了观察者模式的NIO,这样就不用使用循环来不断轮询Selector
状态了。
具体的实现可以自行查阅
网友评论