可以看到java为除boolean外的每一种类型都准备了一个对应的Buffer类
当然,如果要到网络上传输必须得是byte
字段 | 功能 |
---|---|
mark | 用于标记某次读/写的位置,可用reset()回到这里 |
position | 下一个读写的位置 |
limit | 第一个不应该读/写的索引位置,position < limit |
capacity | Buffer能够存放的最大容量 |
我们来看ByteBuffer,它有两个主要的子类,DirectByteBuffer和HeapByteBuffer,DirectByteBuffer使用的是直接内存,HeapByteBuffer用的是堆内存
这里我们以HeapByteBuffer为例
我们可以看到里面有个字节数组叫hb,它就是buffer实际上的存储实现,我们读/写的数据都要经过这个数组。offset是偏移量,一般字节为1
Buffer数据结构
前面提到,简单的说Buffer就是一个封装好的数组,刚初始化的时候,limit和capacity相同
position指向数组头部,mark为-1(只有手动使用过mark()方法mark才会变)。
初始化->对Buffer写
写就是从position索引处开始写,每写一个字节,position就+1(注意position是要写入的索引位置)
// 对应代码
public ByteBuffer put(byte x) {
checkSegment();
// 若position超出limit则抛出异常
hb[ix(nextPutIndex())] = x;
return this;
}
// 若当前位置超出limit则抛出异常
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
// position加1
return position++;
}
写->读
当需要读/写转换时,需要执行flip()方法
public Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
我们可以注意到,limit就是写的最后一个位置,而现在position为0,我们读取数据只能从索引position读到limit,这种限制保证了读取的数据不会越界
读->写(读完了)
若我们是在读取到limit之后才执行flip(),则直接重置就可以了,没啥好说的
读->写(没读完)
若我们需要在读完之前就转换读/写,就要使用compact()函数
public ByteBuffer compact() {
int pos = position();
int rem = limit() - pos;
// 转移还没读完的数据
System.arraycopy(hb, ix(pos), hb, ix(0), rem);
position(rem);
limit(capacity());
discardMark();
return this;
}
compoact()的作用是把还未读完的数据转移到数组开头,并把position置为转移后的未读完数据的下一个索引
其实你会发现,这几个方法,完全没用到mark,mark需要根据场景手动调用mark()函数