java.io 中最为核心的一个概念是流(Stream),面向流的编程。Java中,一个流要么是输入流,要么是输出流,不可能同时即是输入流又是输出流。
java.nio拥有3个核心概念: Selector,Channle与Buffer,在java.nio中,我们是面向块(block)或是缓冲区(buffer)编程的。Buffer本身就是一块内存,底层实现上,他实际上是个数组,数据的读、写通过Buffer来实现的。
java中的7种原生数据类型都有各自对应的Buffer类型,如IntBuffer,ByteBuffer等等,并没有BooleanBuffer类型
除了数组之外,Buffer还提供了了对于数据的结构化访问方式,并且可以追踪到系统的读写过程
Channel指的是可以向其写入数据或是从中读取数据的对象,它类似于java.io的Stream,所有数据的读写都是通过Buffer来进行的,永远不会出现直接向Channel写入的情况,或是直接从Channel读取数据的情况,与Stream不同的是,Channel是双向的,一个流只可能是InputStrem或是OutputStream,Channel打开后则可以进行读取、写入或是读写。
由于Channel是双向的,因此它能更好地反映出底层操作系统的真实情况:在Linux系统中,底层操作系统的通道就是双向的。
关于NIO Buffer中的3个重要状态属性的含义: position,limit与capacity。
capacity:buffer中能包含的元素的数量,不会为负数,永远也不会变化
limit :无法再被读或者写的第一个元素位置的索引,这个数永远不会为负数,或是超过capacity的大小
position:buffer下一个读或者写的元素的位置索引,永远不会超过limit。
往Buffer读写元素有两种情况:1.一种是相对的读写元素之后position位置会发生变化
2.是绝对直接根据给定的索引位置读写元素,不会影响position
marking:标记一下元素的位置
resetting:position重新回到Markingb标记的位置
0<= mark <= position <= limit <= capacity
clear讲Buffer恢复初始化
flip读写操作转换
rewind 把position设置为0
Buffer不是线程安全的
Slice Buffer和原有buffer共享相同的底层数组
只读buufer,我们可以随时将一个普通的Buffer调用asReadOnlyBuffer方法返回一个只读Buffer,但不能将一个只读Buffer转换为读写Buffer
零拷贝
DirectByteBuffer 是直接在堆外内存存放数据,减少了一次拷贝。(如果从堆内内存直接进行与设备操作,如果此时发生GC,数据位置将被移动,发生报错,没办法让此时GC不发生。堆数据想与IO设备交流,要进行一次拷贝)
普通拷贝
首先用户空间像内核空间发出read()调用,内核空间访问磁盘,从磁盘把数据拷贝到内核空间,内核空间在拷贝到用户空间,执行逻辑代码(可无),然后用户空间向内核空间发出write()调用,数据从用户空间拷贝到内核空间数据缓冲区,在拷贝到socketChannel缓冲区,拷贝到协议引擎,write()返回
一共4次上下文切换,5次拷贝。
零拷贝(由操作系统提供)没有数据从内核空间复制到用户空间
首先用户空间发出sendfile()调用,访问磁盘,通过直接内存访问读取到内核空间的缓冲区。将数据写入到目标socket缓冲区(和前面不是同一个缓冲区),拷贝到协议引擎,接收返回信息,sendfile()调用返回。
一共2次上下文切换,3次拷贝
某些系统支持
首先用户空间发出sendfile()调用,访问磁盘,通过直接内存访问读取到内核空间的缓冲区。将数据的描述信息(内存地址,多长)写入到socket缓冲区(利用了Gathering),拷贝到协议引擎,接收返回信息,sendfile()调用返回。
一共2次上下文切换,2次拷贝
Scattering:把buffer对象弄成数组,读取数据
Gathering:把buffer对象弄成数组,写入数据