在java中,Buffer是线性,定长的基本数据类型列表,他不能扩展长度。在nio中,读写操作都是以此为操作单元对象。一次性传输大量数据,避免了多次的copy过程,甚至可以对某些操作直接映射内存。

我主要以ByteBuffer为分析对象,其他基本数据类型的Buffer类似。

 

首先,关于Buffer的创建(direct buffers and non-direct buffers):

一般来说我们建立的缓冲区都是 non-direct buffers,也就是一般说的java堆内存,这些操作系统是无法直接使用进行I/O操作的,必须要内存copy到内核缓存中才能使用。

direct buffer,是通过JNIJava虚拟机外的内存中分配了一块,直接在底层分配的缓存,这样可以避免内存copy的操作提高性能,而且不直接受到gc管理。

allocateDirect方法分配直接内存,当然在某些平台下,并不能保证一定可以分配,需要在之前调用isDirect进行判断,
allocate方法分配堆内存
 

但需要注意的是,一般内存拷贝的消耗,大部分时间可以忽略,而创建和销毁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() 查看本机字节序

 

 

 

 posted on 2013-03-28 14:30  文武双全大星星  阅读(882)  评论(0编辑  收藏  举报