前言:
java.nio包下的Buffer抽象类及其相关实现类,本质上是作为一个固定数量的容器来使用的。
不同于InputStream和OutputStream时的数据容器byte[],Buffer相关实现类容器可以存储不同基础类型的数据,同时可以对容器中的数据进行检索,反复的操作。
Buffer(缓冲区)的工作与Channel(通道)紧密相连。Channel是IO发生时的通过的入口(或出口,channel是双向的),而Buffer是这些数据传输的目标(或来源)。
1.Buffer基本属性
// Invariants: mark <= position <= limit <= capacity
// 标记地址,与reset搭配使用
private int mark = -1;
// 下一个要被读或写的元素的索引
private int position = 0;
// 容器现存元素的计数
private int limit;
// 容器总容量大小,在Buffer创建时被设定
private int capacity;
对于如下一段代码
// position=mark=0
// limit=capacity=10
ByteBuffer buffer = ByteBuffer.allocate(10);
正是通过以上四个属性,实现了数据的反复操作。
2.Buffer的创建
在Buffer的实现类中,使用最广泛的还是ByteBuffer,所以以下示例都是基于ByteBuffer(更具体说是HeapByteBuffer)来说明的,后续会有专门的文章来说明其他基本类型的使用及实现。
根据ByteBuffer的API,我们可以看到以下四种创建方式:
// 1.直接分配capacity大小的Buffer,具体实现类型为HeapByteBuffer
public static ByteBuffer allocate(int capacity)
// 2.直接分配capacity大小的Buffer,具体实现类型为DirectByteBuffer
public static ByteBuffer allocateDirect(int capacity)
// 3.直接使用array作为底层数据
public static ByteBuffer wrap(byte[] array)
// 4.直接使用array作为底层数据,并且指定offset和length
public static ByteBuffer wrap(byte[] array,int offset, int length)
另:有关于HeapByteBuffer和DirectByteBuffer,笔者会在...中会做详细讲解
2.1 allocate创建方式
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)'f');
buffer.put((byte)'i');
buffer.put((byte)'r');
buffer.put((byte)'e');
具体存储图如下:
2.2 wrap创建方式
// wrap(byte[] array,int offset, int length)
// position=offset
// limit=position+length
// capacity=array.length()
ByteBuffer byteBuffer = ByteBuffer.wrap("fire".getBytes(), 1, 2);
具体存储图如下:
3.Buffer的基本操作方法
3.1 添加数据
ByteBuffer buffer = ByteBuffer.allocate(10);
// 1.逐字节存放 ByteBuffer put(byte b)
buffer.put((byte)'h');
// 2.字节存放到对应index ByteBuffer put(int index, byte b);
buffer.put(0,(byte)'h');
// 3.添加字节数组 ByteBuffer put(byte[] src)
byte[] bytes = {'h','e','l','l','o'};
buffer.put(bytes);
// 4.添加其他基础类型 ByteBuffer putInt(int x) ...
buffer.putInt(1);
3.2 获取数据
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("hello".getBytes());
// 1.获取index下的数据
byte b = buffer.get(0);
// 2.逐个获取(需要先flip下,将position置为0)
buffer.flip();
for (int i = 0; i < buffer.remaining(); i++) {
byte b1 = buffer.get();
}
// 3.将数据传输到bytes中
buffer.flip();
byte[] bytes = new byte[5];
ByteBuffer byteBuffer = buffer.get(bytes);
3.3 缓冲区翻转
1)flip是一个比较重要也比较简单的方法,当我们使用put方法将Buffer填充满之后,此时调用get来获取Buffer中的数据时,会获取不到数据,由于get是从当前position来获取数据的,故需要先调用flip来将position置为0
// flip源码如下
// 比较简单,我们也可以手动设置 buffer.limit(buffer.position()).position(0);
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
2)rewind 相对于flip方法而言,rewind也可以重复读取数据,唯一区别就是没有重新设置limit参数
// 源码如下
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
上述两者之间有何不同呢,通过下面一个示例来说明下
ByteBuffer buffer = ByteBuffer.allocate(10);
// 执行完成执行position为4
buffer.put("fire".getBytes());
// 为了测试flip与rewind的不同,重新设置为3
buffer.position(3);
// flip后 pos=0 lim=3 cap=10
buffer.flip();
for (int i = 0; i < buffer.remaining(); i++) {
byte b1 = buffer.get();
System.out.println(b1);// 102 105
}
// 注意:需要单独测试,注释掉上述的flip相关代码
// rewind后 pos=0 limit=cap=10
buffer.rewind();
for (int i = 0; i < buffer.remaining(); i++) {
byte b1 = buffer.get();
System.out.println(b1);// 102 105 114 101
}
总结:针对flip而言,flip之后的Buffer数据操作上限就是上次操作到的位置position
而rewind,上限依旧是limit,可以重新操作全部数据
3.4 缓冲区压缩
有时我们需要从缓冲区中释放已经操作过的数据,然后重新填充数据(针对未操作过的数据,我们是需要保留的)。
我们可以将未操作过的数据(也就是position-limit之间的数据),重新拷贝到0位置,即可实现上述需求。而Buffer中已经针对这种场景实现了具体方法,也就是compact方法
// 示例如下
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());
// 获取一个数据,position前移
buffer.flip();
buffer.get();
// 进行数据压缩
ByteBuffer compact = buffer.compact();
压缩前的buffer:
压缩后的buffer:
相比较而言:将原position到limit之间的数据(1-4,也就是i r e)拷贝到index=0位置,position也就是3,后续新写入数据直接覆盖原position=3的位置数据。
3.5 标记与重置
mark和reset方法,mark用来做标记,reset用来调回到做标记的位置。比较简单,直接看示例
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());
// 直接将position置为2
buffer.position(2);
// 做标记,在position=2位置做标记
buffer.mark();
// 获取position=2的数据,
byte b = buffer.get(); // 114
// 获取下一个position的数据后,执行reset,然后重新获取数据,发现是同一个数据
buffer.reset();
byte c = buffer.get(); // 114
经过reset操作后,position重新回到2,也就是mark时的position,故两次get方法获取的是同一个position的值
3.6 复制
Buffer还提供了快速复制一个Buffer的功能
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());
// 复制buffer
ByteBuffer duplicate = buffer.duplicate();
复制后的duplicate与buffer共享源数据数组,只是拥有不同的position、limit
总结:
Buffer作为数据存储的容器,其有很多的实现类和API,本文中对其基本API进行了分析,后续我们继续对其实现类进行分析。