精准理解 ByteBuffer 中的 capacity、position、limit

ByteBuffer 是 NIO 中提供的一个字节流缓冲区的抽象,用于读取指定长度的字节流,其中有几个变量 capacity、position、limit 不容易理解,经过查阅大量资料,我终于弄明白了其中的端倪。

查看 ByteBuffer 源码发现该类存在几个类似指针的东西来实现管理缓冲区的种种操作。

public abstract class Buffer 
	
...
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
...   
}

在实际使用中 channel 在读取完毕后,通过回调拿到 ByteBuffer 后必须要做的一件事就是 attachment.flip(); ,通常称之为重置缓冲区,那么,这个重置都重置了什么呢?

还是看一下源码

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

首先该方法将 limit 设置为了 position ,然后把 postion 设置为了 0 ,最后把 mark 设置为了 -1,别着急,一步一步分析。

在分析之前先说一下这几个变量是干嘛的,分别代表什么,我用三张图来画一下,更通俗易懂一些。

首先,在初始化 ByteBuffer 缓冲区时指定的长度 就是 capacity,同时 limit 会设置为和 capacity 相同的值。

在这里插入图片描述

解释一下,为什么 capacity 和 limit 的值是10,而不是9,是因为 它俩代表的是长度,而不是角标,这块需要注意下。

当缓冲区读取完毕第一次,变为这样。

这个时候capacity limit 和 postion 值都为10,这个时候可能就比较混乱了,来,屡一下。

缓冲区从0-9被填满,实际上填满了10个盒子,postion 从 0 变为 10,可以理解为 postion 就是目前缓冲区写入到了哪个角标,当然也可以理解为 postion 就是下一个待写入的值应该写入的角标,这么说可能比较难于理解,下面看完就更清楚了。

在这里插入图片描述

当缓冲区满后,将回调用户方法,也就是 CompletionHandler ,CompletionHandler 怎么写?

 public void completed(Integer result, ByteBuffer attachment) {
 	   if (result != -1) {
           pos[0] += result;
           attachment.flip();
           byte[] bytes = new byte[attachment.limit()];
           attachment.get(bytes);
           System.out.println(new String(bytes));
           attachment.clear();
       }
       channel.read(bf, pos[0], bf, this);
}

首先判断是否为读取完毕 reslut 是否等于 -1,如果不是,关键点来了,调用了 flip 方法
,flip 方法都知道是反转一个缓冲区,写了这么多 flip 到底什么是反转缓冲区?不知道!那不行。

老样子,先瞅一眼源码

  public final Buffer flip() {
       limit = position;
       position = 0;
       mark = -1;
       return this;
   }

什么意思?position 值给 limit ,position 重置为0,然后开始读取,那么为什么要这么做?

反转过后缓冲区变成了这样?

在这里插入图片描述

刚才说了,postion 代表记录目前缓冲区占用的位置,而 limit 和 capactiy 代表缓冲区的长度,这块区别来了,其实 limit 和 capactiy 的区别就是,capactiy 代表的是物理长度,limit 代表的是逻辑长度,怎么理解,来看一段代码。

 ByteBuffer allocate = ByteBuffer.allocate(5);
        allocate.limit(4);
        System.out.println(allocate.position() + "====" + allocate.limit() + "======" + allocate.capacity());
        allocate.put(new byte[]{1,2,3,4,5});

输出结果

0====4======5
Exception in thread "main" java.nio.BufferOverflowException
	at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
	at java.nio.ByteBuffer.put(ByteBuffer.java:859)
	at com.accurate.nice.bio.ByteBufferDemo.main(ByteBufferDemo.java:25)

报错啦,为什么?debug 看ByteBuffer的byte数组长度也是5啊,为什么插不进去呢?原因在于插入时并非根据byte数组长度来判断的容量是否足够,而是根据 postion 和 limit 判断的,源码如下,注意 remaining 方法

    public ByteBuffer put(byte[] src, int offset, int length) {
        checkBounds(offset, length, src.length);
        if (length > remaining())
            throw new BufferOverflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            this.put(src[i]);
        return this;
    }
    
    public final int remaining() {
        return limit - position;
    }

当 limit - postion 得到的剩余空间大小不足以插入该 byte 数组时,便会抛出异常,这说明了,缓冲区剩余空间大小是由 limit 逻辑上决定的

那么问题来了,这个 limit 设置为 postion ,还记不记得前面贴的 completed 方法,不记得就回去看一眼,主要是这一句

byte[] bytes = new byte[attachment.limit()];

看到这,你应该看到了真相,之所以 limit 设置为 postion ,是为了读取流的时候使用,有人说,limit 不是和 capactiy 一样大么,我直接用 capactiy 不就行了吗?别忘了,缓冲区不一定每次都可以存满。

总结一下。

capactiy:初始化大小,缓冲区 byte 数组大小
limit:可以理解为逻辑大小,描述这个缓冲区目前有多大
postion:简单理解为下一个字节写到byte数组的哪一个下标。

2020.04.26 晚安

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章