Java NIO
定义
NIO即new IO
我们可以将它想象成一个煤矿,Channel(通道)是一个包含煤层(数据)的矿藏,而 Buffer(缓冲器)则是派送到矿藏的卡车。卡车满载煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和 Channel 交互;我们只是和 Buffer 交互,并把 Buffer 派送到 Channel。Channel 要么从 Buffer 获得数据,要么向 Buffer 发送数据。
新特性
基于通道、缓冲区操作 | 通道(Channel):一个新的IO模型.缓冲区(Buffer):为所有基本数据类型(除了boolean)提供缓冲支持 |
非阻塞 | 提供多路复用IO |
选择器 | 用于单线程监听多个通道的事件 |
Buffer
核心参数
- capacity:它表示一个 Buffer 包含的元素数量,它是非负且恒定不变的。
- position:它是下一个要读或者写的元素的索引,它是非负的且不会超过 limit 的大小。
- limit:它是可以读或者写的最后一个元素的索引,它是非负的且不会超过 capacity 的大小。
- mark:当调用 reset() 方法被调用时,一个 Buffer 的 mark 值会被设定为当前的 position 值的大小。
0 <= mark <= position <= limit <= capacity
Buffer的读模式和写模式的区分
Buffer的本身不区分读写,随意我们可以利用position和limit区分
position=limit时是写,不等于时是读
读写模式的转换
- clear( ) :调用 clear( ) 方法后,我们就可以向 Buffer 里面 put 数据,或者 Channel 能读取 Buffer 里的数据,并且将 limit 设置为 capacity,将 position 设置为 0。
- flip( ) :调用 flip( ) 方法后,我们就可以向 Buffer 里面 get 数据,或者 Channel 能向 Buffer 里写数据,并且将 limit 设置为 position,将 position 设置为 0。
- rewind( ) :调用 flip( ) 方法后,使 Buffer 里的数据可以被重新读取(无论是我们从 Buffer 读,还是 Channel 从 Buffer 读),此时 limit 不变,将 position 设置为 0。
Buffer分类
Heap
Heap存在于JVM堆上,这部分内存的回收和普通对象一样.Heap类型的Buffer对象都包含一个对应基本数据类型的数组属性,数组才是Heap类型的底层缓冲区.但是Heap类型的Buffer不能作为缓冲区参数直接进行系统调用,主要原因如下:
- JVM在GC是可能会移动缓冲区(复制-整理),缓冲区的地址不固定
- 系统调用时,缓冲区需要是连续的,但是数组不可能是连续的(JVM的实现没要求连续)
Heap类型的Buffer进行IO时,JVM需要产生一个临时Direct类型的Buffer,然后进行数据复制,再使用临时Direct的Buffer作为参数进行操作系统调用。这造成很低的效率,主要是因为两个原因:
- 需要把数据从Heap类型的Buffer里面复制到临时创建的Direct的Buffer里面。
- 可能产生大量的Buffer对象,从而提高GC的频率。所以在IO操作时,可以通过重复利用Buffer进行优化。
Direct
Direct类型的buffer,不存在于堆上,而是JVM通过malloc直接分配的一段连续的内存,这部分内存成为直接内存,JVM进行IO系统调用时使用的是直接内存作为缓冲区。
-XX:MaxDirectMemorySize
,通过这个配置可以设置允许分配的最大直接内存的大小(MappedByteBuffer分配的内存不受此配置影响)。
直接内存的回收和堆内存的回收不同,如果直接内存使用不当,很容易造成OutOfMemoryError。JAVA没有提供显示的方法去主动释放直接内存,sun.misc.Unsafe类可以进行直接的底层内存操作,通过该类可以主动释放和管理直接内存。同理,也应该重复利用直接内存以提高效率。
线程安全性
Buffer不是线程安全的
Channel
public static void main(String[] args) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("/Users/wangleshan/Desktop/json/test.json");
FileChannel channel = fileOutputStream.getChannel();
byte[] bytes = "HelloNIO".getBytes(StandardCharsets.UTF_8);
channel.write(ByteBuffer.wrap(bytes));
fileOutputStream.close();
}