一、 Buffer
buffer作为java nio的三大组件之一(buffer、channel、selector),提供了一个字节缓冲区,可以不断的从channel中读取接收到的消息,其提供了一系列的Api,方便用户对数据进行读取及写入。ByteBuffer 是其的一个子类。
二、Bytebuffer
2.1 bytebuffer常用属性
我们使用ByteBuffer通常都是在操作以下几个属性,在下面的方法示例中会逐一描述其含义。
- position
- limit
- capacity
- isReadOnly
2.2 常用操作
- 构建一个ByteBuffer
Bytebuffer.allocate({capacity})
ByteBuffer.allocatrDirect({capacity})
在这里,我们采用了两种方式创建了两个字节缓冲区,那么这两种方式创建的有什么区别? 图中第一种方式创建的ByteBuffer是由jvm在堆中分配的一块内存空间,而第二种方式是直接在直接内存中划分出的一块内存空间。这两种方式存在以下的区别:
- 使用堆内存,分配效率高;而使用直接内存,其分配的效率是比较低的;
- 直接内存相比于java堆内存,少了一次内存拷贝,读写的效率相对较高;
- 直接内存管理相对困难,易造成内存泄露;
- 由于gc的存在,分配在堆内存中会受到gc的影响;使用直接内存则没有这方面的影响
- 常用方法
- flip(): 将当前buffer从写模式切换至读模式。至于什么是读模式以及什么是写模式,需要结合ByteBuffer的position、limit以及capacity来理解: ByteBuffer刚创建时处于Write模式下,此时的position代表的是写操作的起始位置,capacity为创建时的总容量,而limit代表的是可写的终点位置。
随后我们往ByteBuffer中写入数据:
channel.read(buffer) ,假如写入了两个字节的数据,那么此时则成了下面这个样子:
当调用了flip()方法时,将ByteBuffer切换至Read模式,先将limit调整到position的位置,再将position重置为0,在读模式下,position代表的为读操作的起始位置,limit代表的可读的最后一个位置。
不难理解的是,切换为读模式,是为了将写入的数据读取出来,那么需要确定从什么位置开始读,读多少(读到什么位置为止)字节,通过position与limit的能够很方便的解决这些问题。
- clear(): clear方法是将buffer切换至写模式,可以理解为清空buffer中的内容,实际上是将position重置为0,将limit重置为capacity,表示此时能够写入的字节长度为整个buffer的容量;
- compact():
compact方法也是将buffer切换至写模式,与clear不同的是,clear会将整个buffer ‘清空’,如果上一次的读操作并没有读取完buffer中全部的内容,那么进行clear操作会导致消息丢失。而compact是承接上次读取的结果进行。假如在读模式下,我们读取了一个字节,此时的内部结构如下图所示:
此时我们如果使用clear,将position置为0,limit=capacity,那么1这个位置的字节会在写入时被覆盖,则产生丢失情况。 此时需要使用compact,此方法会判断position是否小于等于limit, 如果是,将postion到limit之间的字节拷贝至从起始位置开始(也就是将未读取的字节调整到缓冲区的起始位置)并position重置为两者差值,limit设置为capaticy的大小
int pos = position();
int lim = limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
System.arraycopy(hb, ix(pos), hb, ix(0), rem);
position(rem);
limit(capacity());
discardMark();
此时的结构如下:
其中0的位置为上次读操作未读取完的字节。在posiiton 大于或者等于 limit情况下,compact方法与clear方法没有差别。但是在什么情况下会出现position大于limit的情况本人没有继续了解。
- 最后,一个简单的读写例子
try (FileChannel channel = new FileInputStream("1.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(10);
//从channel读,向buffer写
while (true) {
//打印buffer内容
int read = channel.read(buffer);//返回实际读取的字节数,如果为-1则代表读取到末尾
if (read == -1) {
break;
}
buffer.flip();//切换至读模式
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.print((char) b);
}
System.out.println();
buffer.clear();//切换至写模式
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
仅作为个人学习记录,欢迎大佬指正。