ByteBuffer
有两种类型,一种是
创建ByteBuffer
ByteBuffer b1 = ByteBuffer.allocate(size);
进入 ByteBuffer的allocate方法:
public static ByteBuffer allocate(int capacity) {
...
return new HeapByteBuffer(capacity,capacity);
}
allocate()
中根据参数创建了子类HeapByteBuffer
对象,进入HeapByteBuffer(int,int):
HeapByteBuffer(int cap, int lim) {
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
这里在JVM堆上创建了一个容量为cap的byte数组,并将数组和必要的参数传递给自己父亲ByteBuffer的构造函数 :
ByteBuffer(int mark, int pos, int lim, int cap,
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
ByteBuffer
获取在子类HeapByteBuffer
里创建的byte
数组hb
又调用了自己父亲Buffer
的构造函数:
Buffer(int mark, int pos, int lim, int cap) {
...
this.capacity = cap;
limit(lim);
position(pos);
...
this.mark = mark;
}
}
Buffer
初始化自己的capcity、limit和position
,这几个参数都是HeapByteBuffer
的构造方法传入的,mark
值是-1,先来看limit()方法:
public final Buffer limit(int newLimit) {
...
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
为limit
赋值为newLimit
,此时limit
和capcity
相等,然后对position
和mark
进行判断。
再来看一下position()方法:
public final Buffer position(int newPosition) {
...
position = newPosition;
if (mark > position) mark = -1;
return this;
}
position()
只是将newPositon
赋值给position
,newPosition
是0,所以此时position
是0。
此时ByteBuffer
对象b1
就创建并初始化完毕了,并得到:
position = 0;
offset = 0;
capcity = size; // ByteBuffer 容量
limit = capcity;
mark = -1; // 哨兵
操作
1-put数据
byte aByte = 23;
b1.put(aByte);
进入HeapByteBuffer
的put(byte)
函数:
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
hb
是我们创建的byte
数组,来看一下nextPutIndex():
final int nextPutIndex() {
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
nextPutIndex()
获取下一个插入数据的位置position
,然后递增position
。再来看一下ix()
方法:
protected int ix(int i) {
return i + offset;
}
因为offset
初始化为0 , 所以ix(int)
得到的值就是当前新数据要插入的位置position
,put(byte)
将数据插入到position
位置后,position
下一个插入数据的位置。
假设我们使用put(byte)
插入了6个数据(put(byte[],int,int)
内部也是put(byte)
):
2-remaining获取有效数据的大小
int iSize = b1.remaining();
来看一下remaining()
方法:
public final int remaining() {
return limit - position;
}
由上图可知,此时remaining()
返回的是ByteBuffer中剩余空间的大小,并不是有效数据的个数。所以我们需要先调用flip()
方法:
b1.flip();
来看一下flip()
方法:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip()
是当前ByteBuffer
的状态改变如下:
此时limit
和position
之间的数据即为有效数据。
int iSize = b1.remaining(); // 返回 6
3-get获取数据
byte tByte = b1.get();
看一下get()
方法:
public byte get() {
return hb[ix(nextGetIndex())];
}
这里只有nextGetIndex()
方法我们还不知道,来看一下:
final int nextGetIndex() {
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
nextGetIndex()
获取当前有效数据的位置position
,然后使position
指向下一个有效数据的位置,这里有一个position
是否越界的检查position >= limit
,因为有效数据不能超过limit
。
这样看来get()
只是获取byte
数组hb
小标为当前position
处的元素,并使position
指向下一个有效数据位置。
此时ByteBuffer
的状态图:
同时ByteBuffer
还提供了hasRemaining()
方法,用来判断当前position
是否在有效数据范围内:
public final boolean hasRemaining() {
return position < limit;
}
有了这个方法我们就可以很方便的使用循环提取ByteBuffer
里的数据了:
byte loopByte;
while(b1.hasRemaining()){
loopByte = b1.get();
}
注意:由
hasRemaining()
和remaining()
的源码可知, 它们的返回结果意思取决于limit
是否被position
重置:
1. 未调用执行limit = position
此类的方法,则有 :limit == capacity
,remaining()
表示ByteBuffer
的剩余容量,hasRemaining()
表示判断ByteBuffer
的position
是否超过capacity
。
2. 调用执行limit = position
此类的方法,则有:limit = old_position
,remaining()
表示ByteBuffer
的剩余有效数据的数量,hasRemaining()
表示判断ByteBuffer
的position
是否超过limit
,也就是有效数据是否已经被取完。
4-rewind再次从头读取
加入我们使用get()
读取了4个数据后:
或者将所有有效数据都读完了:
突然有业务要求我们再从头读取该怎么办? 这时可以使用rewind()
方法:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
可以看到rewind()
将position
重置为0,这样我们就可以从头开始读取有效数据。
当然你也可以使用 position(int)
方法为position
设置新值,从有效数据的任意位置进行读取:
public final Buffer position(int newPosition) {
...
position = newPosition;
...
return this;
}
为position
设置新值,如:
b1.position(0);
使用
position(int newPosition)
时一定要满足:
newPosition < limit
,否则读取的将不是有效数据或出错。
5-clear清空数据
b1.clear();
看一下clear()
方法:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
从源码可以看出,clear()
只是将position、limit、mark
重置到刚创建时的状态,并没有对ByteBuffer
的容量capacity
和内部数据做任何操作。如下图:
新插入的数据将直接覆盖原原有数据,就像他们不存在一样。
6-mark哨兵
当使用get(*)或put(*)
获取和插入一些数据后突然又要求回到get(*)或put(*)
之前时的position
位置该怎么办?
除了自己手动通过保存position()
返回的数据:
public final int position() {
return position;
}
再通过position(int)
设置保存的值之外:
public final Buffer position(int newPosition) {
...
position = newPosition;
if (mark > position) mark = -1;
return this;
}
ByteBuffer
本身也为我们提供了有些方法:
b1.mark();
/**
* get()/put()操作后
*/
b1.reset();
先来看一下mark()
方法:
public final Buffer mark() {
mark = position;
return this;
}
它使用mark
记录了当前的position
。
再来看看reset()
方法:
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
它将mark
的值再赋值给position
,这样position
就回到了之前的位置。
注意: 有多种方法会改变
mark
的值,如:clear()
、rewind()
等,确保在mark()
和reset()
之间不要调用此类方法。