JAVA NIO 缓冲区
缓冲区简介
操作系统有用户空间与系统空间的概念,JVM对应的JAVA进程是位于用户空间的,处于该空间的进程不能直接访问硬件设备,当JAVA进程要进行I/O操作时,只能通过系统调用将控制权交给内核,内核准备好进程所需要的数据,将这些数据拷贝到用户空间缓冲区(如下图所示)。
JAVA中将缓冲区抽象为Buffer类,Buffer类中几个重要属性和方法
mark(标记):标记位,初始值为-1。通过调用mark()方法将mark设置为position,调用reset()方法将position设置为mark值,也就是还原到上一次标记的位置。在调用reset()方法时如果mark没有设置会抛出InvalidMarkException这个异常。
position(位置):表示Buffer中第一个可以被读取或者写入的位置,调用put()方法时先调用nextPutIndex()获取当前位置,然后会将position+1。
limit(限制):表示Buffer中第一个不可读取或者写入的位置,调用limit(int)设置limit的值。
capacity(容量):表示Buffer初始大小,一但创建后不可改变。
这几个属于有个不变式:0<=mark<=position<=limit<=capacity.
clear():清除缓冲区,重置mark,position设置为0,limit设置为capacity;
reset():将position设置为mark;
flip():将limit设置为position的位置,position设置为0.该方法常常与compact()方法配合使用,先看compact()方法,再看它们如何配合的;
compact():这是Buffer的子类拥有的方法,文档中的解释是这样的:压缩此缓冲区,将缓冲区当前位置和界限之间的数据(如果有)复制到缓冲区的开始处。即将索引 p = position() 处的数据复制到索引0处,将索引 p + 1 处的 int 复制到索引 1 处,依此类推,直到将索引 limit() - 1 处的 int 复制到索引 n = limit() - 1 - p 处。然后将缓冲区的位置设置为 n+1,并将其界限设置为其容量。如果已定义了标记,则丢弃它。相当于把position与limit之间的数据整体移动到开始位置,其实是把0到position这段数据擦除掉。
以下是通过缓冲区将字节从一个channel复制到另一个channel的代码片段:
[java]
buf.clear();
while (in.read(buf) >= 0 || buf.position != 0) {
buf.flip();
out.write(buf);
buf.compact();
}
buf.clear();
while (in.read(buf) >= 0 || buf.position != 0) {
buf.flip();
out.write(buf);
buf.compact();
}
以上代码可以看出由于buf容量有限不可能将数据一次性写入buf,需要分批写入。写入时positon有变化,需要将position设置为0才能读取,读取的最大长度其实就是position所在的位置,flip()方法做的就是这个事儿。
但out.write(buf)有可能只写入了buf中的部分数据,compact做的事就是把这部分没用的数据覆盖掉,等待下次将剩余数据写入通道。FileChannel的write方易做图修改buffer的position值,满足while循环条件,进入循环体进行下一次读取。
rewind():重绕缓冲区,将position设置为0,如果想把一个缓冲区的数据写入多个目标需要用到此方法。
字节缓冲区
Buffer类是抽象类,它有很多子类: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer.以ByteBuffer来分析缓冲区的实现。
ByteBuffer也是一个抽象类,它有两种实现:HeapByteBuffer 和 DirectByteBuffer。
HeapByteBuffer对象和普通对象一样,分配在JVM的堆上,由垃圾回收器回收,在底层维护着一个字节数组,数组长度不可变。
调用allocate方法可以分配一个新的字节缓冲区。 新缓冲区的位置将为零,其界限将为其容量,其标记是不确定的。它将具有一个底层实现数组,且其数组偏移量将为零。除此外还可以通过wrap方法将现有字节数组包装成一个缓冲区,修改缓冲区内容会同时修改字节数组。
DirectByteBuffer是通过底层的malloc()分配空间,在JVM之外,其实现细节可以参考java.nio.Bits类。当有大量文件需要以内存映射的形式映射到内存中时,首选DirectByteBuffer。
直接缓冲区
字节缓冲区要么是直接的,要么是非直接的,最大的区别是于内存位置不同。直接缓冲区可以通过allocateDirect方法来创建,也可以通过内存映射来创建,如果为直接字节缓冲区JVM会尽最大努力直接在此缓冲区上执行本机 I/O 操作。
非直接缓冲区写入步骤:
1.创建一个临时的直接ByteBuffer对象。
2.将非直接缓冲区的内容复制到临时缓冲中。
3.使用临时缓冲区执行低层次I/O操作。
4.临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
如果采用直接缓冲区会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/o操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销。
补充:软件开发 , Java ,