JAVA I/O机制(二)

Posted by ShiYu on 2017-12-12

NIO组成

NIO主要由以下3个核心部分组成:

  1. Channels
  2. Buffers
  3. Selectors

NIO与标准IO的区别主要是NIO面Buffer而IO面向流,通过Buffer从Channel中读取数据或向Channel写入数据,通过Selector监听Channel从而实现单线程处理多个channel,此外NIO是可以设置为非阻塞的。

Buffer的工作方式

Buffer通过几个变量保存Buffer当前位置状态:

Buffer结构

  1. capacity:缓冲区数组总长度
  2. position:下一个要操作的数据元素位置
  3. limit:缓冲区数组中不可操作的下一个元素位置,limit<=capacity
  4. mark:用于记录当前position的前一个位置或者默认是0

buffer写入3个字节后,各个变量位置如下图:
Buffer写入3个字节

此时如果想读取buffer内容,需要调用flip()方法,调用该方法后各变量位置如下图:
Buffer读取

上图没有说明mark的作用,当我们调用mark()时,它将记录当前position的前一个位置,当我们调用reset时,position将恢复mark记录下来的值。

通过Channel获取I/O数据首先要经过操作系统的缓冲区,再将数据复制到Buffer中,从操作系统缓冲区到用户缓冲区复制数据比较耗费性能,Buffer提供了另外一种直接操作操作系统缓冲区的方式,即ByteBuffer.allocateDirector(size),这个方法返回的DirectByteBuffer就是与底层存储空间关联的缓冲区,它通过Native代码操作非JVM堆的内存空间。每次创建或者释放的时候都调用一次System.gc()。在使用DirectByteBuffer时可能会引起JVM内存泄漏问题。

NIO数据访问方式

NIO提供了比传统的文件访问方式更好的方法,一个是FileChannel.transferToXXX,另一个是FileChannel.map。

  1. FileChannel.transferXXX

FileChannel.transferXXX方法使数据直接在内核空间移动,与传统访问方式相比可以减少数据从内核到用户空间的复制。

下图是传统的数据访问方式:

传统读取文件

下图是FileChannel.transferXXX的访问方式:

nio读取文件

  1. FileChannel.map

FileChannel.map将文件按照一定大小块映射为内存区域,当程序访问这个内存区域时将直接操作这个文件数据,省去了将数据从内核空间向用户空间复制的损耗,这种方式适合大文件的只读性操作,如大文件的MD5校验。