ByteBuffer

192 阅读4分钟

一、 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在堆中分配的一块内存空间,而第二种方式是直接在直接内存中划分出的一块内存空间。这两种方式存在以下的区别:

  1. 使用堆内存,分配效率高;而使用直接内存,其分配的效率是比较低的;
  2. 直接内存相比于java堆内存,少了一次内存拷贝,读写的效率相对较高;
  3. 直接内存管理相对困难,易造成内存泄露;
  4. 由于gc的存在,分配在堆内存中会受到gc的影响;使用直接内存则没有这方面的影响
  • 常用方法
  1. flip(): 将当前buffer从写模式切换至读模式。至于什么是读模式以及什么是写模式,需要结合ByteBuffer的position、limit以及capacity来理解: ByteBuffer刚创建时处于Write模式下,此时的position代表的是写操作的起始位置,capacity为创建时的总容量,而limit代表的是可写的终点位置。

image.png 随后我们往ByteBuffer中写入数据: channel.read(buffer) ,假如写入了两个字节的数据,那么此时则成了下面这个样子:

image.png 当调用了flip()方法时,将ByteBuffer切换至Read模式,先将limit调整到position的位置,再将position重置为0,在读模式下,position代表的为读操作的起始位置,limit代表的可读的最后一个位置。

image.png 不难理解的是,切换为读模式,是为了将写入的数据读取出来,那么需要确定从什么位置开始读,读多少(读到什么位置为止)字节,通过position与limit的能够很方便的解决这些问题。

  1. clear(): clear方法是将buffer切换至写模式,可以理解为清空buffer中的内容,实际上是将position重置为0,将limit重置为capacity,表示此时能够写入的字节长度为整个buffer的容量;
  2. compact(): compact方法也是将buffer切换至写模式,与clear不同的是,clear会将整个buffer ‘清空’,如果上一次的读操作并没有读取完buffer中全部的内容,那么进行clear操作会导致消息丢失。而compact是承接上次读取的结果进行。假如在读模式下,我们读取了一个字节,此时的内部结构如下图所示: image.png 此时我们如果使用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();

此时的结构如下:

image.png 其中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);  
    }  
}

仅作为个人学习记录,欢迎大佬指正。