精準理解 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 晚安

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