NIO

189 阅读3分钟

0 NIO和IO的区别

比较项IONIO
面向流面向缓存区
阻塞IO非阻塞IO
选择器

IO 简单理解是一个管道,用于在数据源和目的地之间进行数据的传输。使用的场景有文件,网络和磁盘。 传统的IO是基于流的,流是单向的,有输出流和输入流。从流传输单元的角度,有字节流和字符流等。

1 缓存区的读写

怎么理解第一个特点,面向缓存区。

String str = "abcd";
String infoLog = "position=%s,capacity=%s,limit=%s";
ByteBuffer byteBuffer = ByteBuffer.allocate(6);
System.out.println("初始化: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));
byteBuffer.put(str.getBytes());
System.out.println("写后: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));
byteBuffer.flip();
System.out.println("调用flip()开始读: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println("读到的数据**" +new String(bytes) +"**");

System.out.println("读完后: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));
byteBuffer.rewind();
System.out.println("调用rewind(),可重复读: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));

byteBuffer.clear();
System.out.println("清空缓冲区: " + String.format(infoLog,byteBuffer.position(),byteBuffer.capacity(),byteBuffer.limit()));

输出信息如下

初始化: position=0,capacity=6,limit=6
写后: position=4,capacity=6,limit=6
调用flip()开始读: position=0,capacity=6,limit=4
读到的数据**abcd**
读完后: position=4,capacity=6,limit=4
调用rewind(),可重复读: position=0,capacity=6,limit=4
清空缓冲区: position=0,capacity=6,limit=6

缓存区这种方式和流有着明显的区别,在数据源和输出之间通过缓存区的方式进行数据的传输,首先会开辟一个缓存区,数据源向缓存区写数据,写完后缓存区切换到读模式,输出就会来读这个缓存区。打个比方,数据源和目的地是两个火车站,缓存区是有固定节数的列车,整个输出传输的过程就是火车先在数据源火车站装填货物,然后开到目的地,目的地火车站进行卸货。不过这个模型和正事有所区别的地方在于,火车卸完货物后给空了,但是模型里如果缓存区写入部分如果没有覆盖,这个数据可以一直重复进行读取。

image.png

2 直接缓存区和非直接缓存区

image.png

非直接缓存区数据会在内核态和用户态空间之间来回拷贝。 直接缓存区在数据源和目的地之间通过物理内存建立了物理内存映射文件。这种方式避免了数据来回拷贝,也就是零拷贝,提高了数据传输的效率,但是这种方式也不是没有弊端,比如不安全。这块数据是堆外内存,垃圾回收的方式不能控制。

image.png

3 通道

image.png

image.png

通道用于源节点和目标节点之间的链接。在Java NIO中负责缓冲区数据的运输。 Channel本身不存储数据,因此需要配合缓冲区进行传输。

分散读取 image.png

聚集写入

image.png

RandomAccessFile raf1 = new RandomAccessFile("/Users/xxx/p1_mysql2hive","rw");
// 获取通道
FileChannel channel1 = raf1.getChannel();

// 分配指定大小的缓存区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);

// 分散读取
ByteBuffer[] buffers = {buf1,buf2};
channel1.read(buffers);

for (ByteBuffer byteBuffer :buffers){
    byteBuffer.flip();
}
// 读文件的前100字节
System.out.println(new String(buffers[0].array(),0,buffers[0].limit()));
// 读文件接下来的 1024字节
System.out.println(new String(buffers[1].array(),0,buffers[1].limit()));

// 聚集写入
RandomAccessFile raf2 = new RandomAccessFile("/Users/xxx/2.txt","rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(buffers);

// 从结果中可以看到有 1124字节的数据从数据源文件写到了 raf2指向的文件中