在java中,Buffer是线性,定长的基本数据类型列表,他不能扩展长度。在nio中,读写操作都是以此为操作单元对象。一次性传输大量数据,避免了多次的copy过程,甚至可以对某些操作直接映射内存。
我主要以ByteBuffer为分析对象,其他基本数据类型的Buffer类似。
首先,关于Buffer的创建(direct buffers and non-direct buffers):
一般来说我们建立的缓冲区都是 non-direct buffers,也就是一般说的java堆内存,这些操作系统是无法直接使用进行I/O操作的,必须要内存copy到内核缓存中才能使用。
direct buffer,是通过JNI在Java虚拟机外的内存中分配了一块,直接在底层分配的缓存,这样可以避免内存copy的操作提高性能,而且不直接受到gc管理。
但需要注意的是,一般内存拷贝的消耗,大部分时间可以忽略,而创建和销毁direct buffers的消耗比较大。
所以,除非必要,如超大文件等 ,一般操作non-direct方式是没有问题的,或者你能确定这块内存会长期存在,并和外界如网络等,有频繁交互,可以使用Direct buffers.
Buffer有四个关键的索引:
capacity: 缓冲区总大小。
position:下个要读取的元素位置。
limit: 第一个不可读写的元素位置。
mark: 用户选定的position的前一个位置,或0
0<= mark <= position <= limit <= capacity
首先以下段代码为例:
ByteBuffer buffer = ByteBuffer.allocate(10); for(byte i = 0; i < 16; i++) { buffer.put(i); }
这时内存中实际存在的是这样一个字节数组:
(图一)
position = 8 ,因为经过循环put之后,position在不断变化。
limit = capacity = 10
mark = -1
下面介绍几个重要的操作:
如图一所示,我们用相对位置的put/get方法时,都是从下一个位置都是position索引位置开始。
如果我们想从buffer头开始读写数据,除了用绝对位置的get/put方法,我们只有通过设置position来做到
flip和rewind都可以达到相同的效果,区别在于对limit的设置:
rewind(),将position设为0:
(图二)
(图二)
flip(),将limit设为position值,再将position设为0来实现:
(图三)
clear():清除缓冲区。
这里实际并没有修改缓冲区内容,只是简单的重置了几个重要的buffer位置索引,
在这里恰巧跟rewind(图二)表现的一样,实际上,他将几个重要的索引都重置了。
(图四)
compact(): 压缩Buffer数据,将position与limit之间的元素复制到缓冲区的开始位置。
如果我们在(图一)的状态调用compact,结果如下:
我们可以看到前两个字节和position到limit之间元素都是一样的0了。
duplicate(),用于创建一个和原始缓冲区共享内容的新缓冲区。 但每一个新缓冲区有自己的关键索引值, limit, capacity,mark都为原始缓冲区索引值。
slice(), 创建了一个共享了原始缓冲区子序列的新缓冲区,新缓冲区的position=0,limit和capacity都为remaining()值,mark未定义。
关于字节序问题,由于ByteBuffer以字节为单位,没有此问题,其他的多字节buffer则需要注意
主要通过:
ByteOrder order() 查看现在字节序
ByteBuffer order(ByteOrder order) 设置字节序
ByteOrder nativeOrder() 查看本机字节序