java.nio.Buffer类图文解析
Posted wkw1125
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java.nio.Buffer类图文解析相关的知识,希望对你有一定的参考价值。
首先导读:ByteBuffer常用方法详解,我是从该文章开始了解,作图加深理解。
Buffer
ByteBuffer(以及其他类型缓冲区,如CharBuffer、IntBuffer等)继承自抽象类Buffer,用于在堆内存中预留一部分空间对IO数据做临时存储,以提高读写效率。各子类不同之处在于get/put数据的不同,而关于缓冲区读写区域的管理全部由父类Buffer负责。继承关系盗图如下:
4个属性
Buffer的3个关键成员变量:
final int capacity;
int limit;
int position = 0;
以上3个成员变量都有同名方法获取,以及1个关键的衍生值:
public final int remaining() return limit - position;
Buffer本质是一个数组,通过以上4个值管理这个工作数组的可读/写区域(我将其称之为“活动区域”),正确理解这些值是用好所有Buffer子类的关键。
结合示意图来理解这4个值:
【图1 Buffer示意图】
(假设,该Buffer处于读模式,即用户正在从该buffer读取数据)
- capacity:该buffer能容纳的数据总长度,即工作数组的长度,这里是16,数组元素下标为0~15。
- position:读取位置起点3,get()操作返回该位置的值”h”,并将该值+1。
- limit:可读取有效数据的界限,类似于数组的length。当前值为13,即get()操作最多读取到position=13-1=12的位置。
- remaining=limit - position:剩余可读取数据的数量,这里还有10个数据可以读取。
数值关系:position <= limit <= capacity
注意,当position == limit 时表示无数据可读取(或无位置可写入)。
为了理解这些属性,需要先理解一下Buffer的工作方式:
Buffer工作方式
对用户来说,对同一个buffer的使用分为两种操作:往buffer输入数据,之后从buffer读取数据。缓冲区的意义在于,读和写的节奏可能不一样,有可能写入10个数据却只读取了3个,这时新来数据需要写入,也应该可以接着之前的数据末尾继续写入,而不能把未读取的7个数据丢了。
Buffer的工作数组长度capacity在初始化时就确定并且不可改变,position与limit用于划分出数组的“活动区域”,remaining()计算得到的则是该区域的长度。对Buffer的读写操作实际上都是针对这块区域的操作:该“活动区域”在写模式下表示Buffer可以写入数据的位置范围;在读模式下表示Buffer中可以读取的有效数据的范围。
这里之所以强调有效,是由于Buffer始终使用同一个数组处理读和写,一个位置上的数据一旦被读取了,它就失效了,下次写入时这个位置就允许被写入新数据。Buffer处理失效数据,不是将数据移除,而是将position标志位进行变化。对于已读取的数据,只需要将其排除在“活动区域”之外,就不会被读取。如Buffer.clear()仅仅是将标志位position=0,limit=capacity,表示整个数组都可以被写入(覆盖)而已。
若对【图1】中的Buffer调用get方法,即buffer处于读模式,那么可读取下标为3~12之间的数据“helloworld”,其他区间的数据可能存在,但一定是无效的(如这里”hi!”意味着已被读取)。若对其调用put方法,即buffer处于写模式,那么将从位置3开始写入新数据,(这往往意味着”hi!”是已写入的数据)。
因此,要正确处理Buffer的读写操作,就要处理好各个标志位在读写模式切换时的变化。这个变化由两个方法负责:flip()、compact().
2个方法
flip()翻转
flip():(向前)翻转“活动区域”,准备开始读取
//Flips this buffer
//该方法由抽象类Buffer定义
public final Buffer flip()
limit = position;
position = 0;
return this;
为何叫“翻转”?看图就理解了:
【图2:flip()示意图】
上图中绿色的“活动区域”经过flip()后,像翻书一样地向前(向左)翻转到了下图中绿色部分。将活动区域翻到开头后,就可以使用get()从头开始读取数据。因此,flip()一般用于准备读取buffer。
compact()压实
compact():(向前)压实“活动区域”,准备开始写入。
/**
* Compacts this byte buffer.
* The remaining bytes will be moved to the head of the
* buffer, starting from position zero. Then the position is set to remaining(); the limit is set to capacity; ...
*/
//该方法由Buffer的子类定义和实现
public abstract ByteBuffer compact();
该方法由Buffer的子类定义和实现,从注释可知:将Buffer中剩余的数据区域移动到头部,再将“活动区域”设置为除数据区域外的末尾部分。
看图:
【图3:compact()示意图】
上图中绿色的“活动区域”经过compact()后,往前(往左)压实,挤掉了无效的0~2位置的三个数据;并重新将“活动区域”指向了尾部。压实后,就可以使用put()从绿色部分末尾,开始写入新数据。因此,compact()一般用于准备写入buffer。
ByteBuffer API总结
掌握4个值、2个方法后,Buffer类的使用参照api文档就可以了。
下面以ByteBuffer为例,总结下常用的几个方法,详细见开篇导读文章。
方法 | 解释 |
---|---|
wrap(byte[]) | 静态方法,以初始数据创建Buffer(处于读模式) |
capacity() | 获取容量 |
position() | 获取当前位置 |
limit() | 获取当前读写界限 |
remaining() | 获取当前可读写数量,值为limit()-position() |
flip() | 翻转活动区域,准备开始从头部读取 |
compact() | 压实活动区域,准备开始从尾部写入 |
mark() | 记录当前位置position,以备恢复 |
reset() | 将当前位置position恢复到mark位置,其他不变 |
rewind() | 将当前位置position恢复到0位置,mark设为-1,其他不变 |
get() | 读取当前位置数据,并且移动position |
get(int) | 读取指定位置数据,但不移动position |
put(byte) | 末尾位置写入,并且移动position |
put(int,byte) | 指定位置写入,但不移动position |
clear() | 将整个buffer置为写模式的活动区域,可以从头写到尾 |
order(ByteOrder) | 字节顺序,对同一ByteBuffer的读写应保持一致,详见ByteOrder |
注意:各种get/put方法是否影响当前位置,需根据api说明使用。
注意点
- ByteBuffer.warp(byte[])以一个数组创建buffer后,该buffer默认处于读模式,即position=0,limit=capacity。此时若使用put进行写入,将从0位置开始覆盖掉初始化数组的数据。因此,若初始化的数据是有用数据,在写入开始前应使用compact()压实后,从尾部开始写入。
- 一定要注意get/put重载的方法是否影响当前位置,在读取、写入前都要用remaining()进行可读/写判断
- remaining()是动态计算得到,谨记在get/put后,该值可能发生改变。
- position==limit时,get/put越界。即position=limit-1是最后一个数据
测试结果
测试了几个api证实本文理解正确
get
ByteBuffer after wrap:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12,remaining=12
ByteBuffer after get2:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=2,limit=12;remaining=10
ByteBuffer after flip:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=2;remaining=2
ByteBuffer after comp:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=2,limit=12;remaining=10
flip
ByteBuffer after wrap:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12,remaining=12
ByteBuffer after flip:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=0;remaining=0
ByteBuffer after comp:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12;remaining=12
put
ByteBuffer after wrap:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12,remaining=12
ByteBuffer after put1:[ 72, 73, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=2,limit=12;remaining=10
ByteBuffer after flip:[ 72, 73, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=2;remaining=2
ByteBuffer after comp:[ 72, 73, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=2,limit=12;remaining=10
compact after wrap
ByteBuffer after wrap:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12,remaining=12
ByteBuffer after comp:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=12,limit=12;remaining=0
ByteBuffer after flip:[104, 101, 108, 108, 111, 44, 119, 111, 114, 108, 100, 33], position=0,limit=12;remaining=12
以上是关于java.nio.Buffer类图文解析的主要内容,如果未能解决你的问题,请参考以下文章