深入理解JAVA中的NIO

传统的 IO 流还是有很多缺陷的,尤其它的阻塞性加上磁盘读写本来就慢,会导致 CPU 使用效率大大降低。

所以,jdk 1.4 发布了 NIO 包,NIO 的文件读写设计颠覆了传统 IO 的设计,采用通道+缓存区使得新式的 IO 操作直接面向缓存区,并且是非阻塞的,对于效率的提升真不是一点两点,我们一起来看看。

通道 Channel

我们说过,NIO 的核心就是通道和缓存区,所以它们的工作模式是这样的:

深入理解JAVA中的NIO

通道有点类似 IO 中的流,但不同的是,同一个通道既允许读也允许写,而任意一个流要么是读流要么是写流。

但是你要明白一点,通道和流一样都是需要基于物理文件的,而每个流或者通道都通过文件指针操作文件,这里说的「通道是双向的」也是有前提的,那就是通道基于随机访问文件『RandomAccessFile』的可读可写文件指针。

『RandomAccessFile』是既可读又可写的,所以基于它的通道是双向的,所以,「通道是双向的」这句话是有前提的,不能断章取义。

基本的通道类型有如下一些:

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

FileChannel 是基于文件的通道,SocketChannel 和 ServerSocketChannel 用于网络 TCP 套接字数据报读写,DatagramChannel 是用于网络 UDP 套接字数据报读写。

通道不能单独存在,它永远需要绑定一个缓存区,所有的数据只会存在于缓存区中,无论你是写或是读,必然是缓存区通过通道到达磁盘文件,或是磁盘文件通过通道到达缓存区。

即缓存区是数据的「起点」,也是「终点」,具体这些通道到底有哪些不同以及该如何使用,基本实现如何,我们介绍完『缓存区』概念后,再做详细学习。

缓存区 Buffer

Buffer 是所有具体缓存区的基类,是一个抽象类,它的实现类有很多,包含各种类型数据的缓存。

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

MappedByteBuffer

我们以 ByteBuffer 为例进行学习,其余的缓存区也都是基于字节缓存区的,只不过多了一步字节转换过程而已,MappedByteBuffer 是一个特殊的缓存方式,我们会单独介绍。

Buffer 中有几个重要的成员属性,我们了解一下:

深入理解JAVA中的NIO

mark 属性我们已经不陌生了,用于重复读。capacity 描述缓存区容量,即整个缓存区最大能存储多少数据量。address 用于操作直接内存,区别于 jvm 内存,这一点待会说明。

而 position 和 limit 我想用一张图结合解释:

深入理解JAVA中的NIO

由于缓存区是读写共存的,所以不同的模式下,这两个变量的值也具有不同的意义。

写模式下,所谓写模式就是将缓存区中的内容写入通道。position 代表下一个字节应该被写出去的字节在缓存区中的位置,limit 表示最后一个待写字节在缓存区的位置。

读模式下,所谓读模式就是从通道读取数据到缓存区。position 代表下一个读出来的字节应当存储在缓存区的位置,limit 等于 capacity。

相关的读写操作细节,待会会和大家一起看源码,以加深对通道和缓存区协作工作的原理,这里我们先讨论一个大家可能没怎么关注过的一个问题。

JVM 内存划分为栈和堆,这是大家深入脑海的知识,但是其实划分给 JVM 的还有一块堆外内存,也就是直接内存,很多人不知道这块内存是干什么用的。

这是一块物理内存,专门用于 JVM 和 IO 设备打交道,Java 底层使用 C 语言的 API 调用操作系统与 IO 设备进行交互。

例如,Java 内存中有一个字节数组,现在调用流将它写入磁盘文件,那么 JVM 首先会将这个字节数组先拷贝一份到堆外内存中,然后调用 C 语言 API 指明将某个连续地址范围的数据写入磁盘。

读操作也是类似,而 JVM 额外做的拷贝工作也是有意义的,因为 JVM 是基于自动垃圾回收机制运行的,所有内存中的数据会在 GC 时不停的被移动,如果你调用系统 API 告诉操作系统将内存某某位置的内存写入磁盘,而此时发生 GC 移动了该部分数据,GC 结束后操作系统是不是就写错数据了。

所以,JVM 对于与外围 IO 设备交互的情况下,都会将内存数据复制一份到堆外内存中,然后调用系统 API 间接的写入磁盘,读也是类似的。由于堆外内存不受 GC 管理,所以用完一定得记得释放。

理解这一个小知识是看懂源码实现的前提,不然你可能不知道代码实现者在做什么。好了,那我们就先来看看读操作的基本使用与源码实现。

深入理解JAVA中的NIO

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgpsw.html