JDK源碼閱讀:ByteBuffer

Buffer是Java NIO中對於緩衝區的封裝。在Java BIO中,全部的讀寫API,都是直接使用byte數組做爲緩衝區的,簡單直接。可是在Java NIO中,緩衝區這一律念變得複雜,多是對應Java堆中的一塊內存,也多是對應本地內存中的一塊內存。而byte數組只能用來指定Java堆中的一塊內存,因此Java NIO中設計了一個新的緩衝區抽象,涵蓋了不一樣類型緩衝區,這個抽象就是Buffer。數組

Buffer

Buffer是Java NIO中對於緩衝區的抽象。是一個用於存儲特定基本數據類型的容器。Buffer是特定基本數據類型的線性有限序列。安全

Java有8中基本類型:byte,short,int,long,float,double,char,boolean,除了boolean類型外,其餘的類型都有對應的Buffer具體實現:函數

Buffer抽象類定義了全部類型的Buffer都有的屬性和操做,屬性以下:this

  • capacity:緩衝區的容量,在緩衝區創建後就不能改變
  • limit:表示第一個不能讀寫的元素位置,limit不會大於capacity
  • position:表示下一個要讀寫的元素位置,position不會大於limit
  • mark:用於暫存一個元素位置,和書籤同樣,用於後續操做

全部的Buffer操做都圍繞這些屬性進行。這些屬性知足一個不變式:0<=mark<=position<=limit<=capacityspa

新建的Buffer這些屬性的取值爲:操作系統

  • position=0
  • limit=capacity=用戶設置的容量
  • mark=-1

直接看定義比較抽象,能夠看一下示意圖,下圖是一個容量爲10的Buffer:線程

ByteBuffer的具體實現

全部Buffer實現中,最重要的實現是ByteBuffer,由於操做系統中全部的IO操做都是對字節的操做。當咱們須要從字節緩衝區中讀取別的數據類型才須要使用其餘具體類型的Buffer實現。設計

ByteBuffer也是一個抽象類,具體的實現有HeapByteBuffer和DirectByteBuffer。分別對應Java堆緩衝區與堆外內存緩衝區。Java堆緩衝區本質上就是byte數組,因此實現會比較簡單。而堆外內存涉及到JNI代碼實現,較爲複雜,本次咱們以HeapByteBuffer爲例來分析Buffer的相關操做,後續專門分析DirectByteBuffer。code

ByteBuffer的類圖以下:ip

讀寫Buffer

Buffer做爲緩衝區,最主要的做用是用於傳遞數據。Buffer提供了一系列的讀取與寫入操做。由於不一樣類型的Buffer讀寫的類型不一樣,因此具體的方法定義是定義在Buffer實現類中的。與讀寫相關的API以下:

1

2

3

4

5

6

7

8

9

byte get()

byte get(int index)

ByteBuffer get(byte[] dst, int offset, int length)

ByteBuffer get(byte[] dst)

 

ByteBuffer put(byte b)

ByteBuffer put(int index, byte b)

ByteBuffer put(ByteBuffer src)

ByteBuffer put(byte[] src, int offset, int length)

Buffer的讀寫操做能夠按照兩種維度分類:

  • 單個/批量:
    • 單個:一次讀寫一個字節
    • 批量:一次讀寫多個字節
  • 相對/絕對:
    • 相對:從Buffer維護的position位置開始讀寫,讀寫時position會隨之變化
    • 絕對:直接指定讀寫的位置。指定index的API就是絕對API

接着咱們來看看這些函數在HeapByteBuffer中是如何實現的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

final byte[] hb;    // 做爲緩衝區的byte數組             

final int offset;   // 指定緩衝區的起始位置

 

public byte get() {

    // get操做就是直接從數組中獲取數據

    return hb[ix(nextGetIndex())];

}

 

public byte get(int i) {

    // 從指定位置獲取數據,是絕對操做,只需檢查下標是否合法

    return hb[ix(checkIndex(i))];

}

 

// 獲取下一個要讀取的元素的下標

// position的定義就是下一個要讀寫的元素位置,

// 因此這裏是返回position的當前值,而後再對position進行加一操做

final int nextGetIndex() {                          // package-private

    if (position >= limit)

        throw new BufferUnderflowException();

    return position++;

}

 

// 由於支持偏移量,因此算出來的下標還須要加上偏移量

protected int ix(int i) {

    return i + offset;

}

單字節put與get邏輯同樣。看一下批量get是如何實現的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public ByteBuffer get(byte[] dst) {

    return get(dst, 0, dst.length);

}

 

public ByteBuffer get(byte[] dst, int offset, int length) {

    // 檢查參數是否越界

    checkBounds(offset, length, dst.length);

    // 檢查要獲取的長度是否大於Buffer中剩餘的數據長度

    if (length > remaining())

        throw new BufferUnderflowException();

    // 調用System.arraycopy進行數組內容拷貝

    System.arraycopy(hb, ix(position()), dst, offset, length);

    // 更新position

    position(position() + length);

    return this;

}

能夠看出,HeapByteBuffer是封裝了對byte數組的簡單操做。對緩衝區的寫入和讀取本質上是對數組的寫入和讀取。使用HeapByteBuffer的好處是咱們不用作各類參數校驗,也不須要另外維護數組當前讀寫位置的變量了。

同時咱們能夠看到,Buffer中對於position的操做沒有使用鎖進行保護,因此Buffer不是線程安全的。

Buffer的模式

雖然JDK的Java Doc並無提到Buffer有模式,可是Buffer提供了flip等操做用於切換Buffer的工做模式。在正確使用Buffer時,必定要注意Buffer的當前工做模式。不然會致使數據讀寫不符合你的預期。

Buffer有兩種工做模式,一種是接收數據模式,一種是輸出數據模式。

新建的Buffer處於接收數據的模式,能夠向Buffer放入數據,放入一個對應基本類型的數據後,position加一,若是position已經等於limit了還進行put操做,則會拋出BufferOverflowException異常。

這種模式的Buffer能夠用於Channel的read操做緩衝區,或者是用於相對put操做。

好比向一個接受數據模式的Buffer put5個byte後的示例圖:

由於Buffer的設計是讀寫的位置變量都使用position這個變量,因此若是要從Buffer中讀取數據,要切換Buffer到輸出數據模式。Buffer提供了flip方法用於這種切換。

1

2

3

4

5

6

public final Buffer flip() {

    limit = position;

    position = 0;

    mark = -1;

    return this;

}

切換後的效果圖:

而後就能夠從Buffer中讀取數據了。每次讀取一個元素,position就會加一,若是position已經等於limit還進行讀取,會拋出BufferUnderflowException異常。

能夠看出Buffer自己沒有一個用於存儲模式的變量,模式的切換隻是position和limit的變換而已。

flip方法只會把Buffer從接收模式切換到輸出模式,若是要從輸出模式切換到接收模式,可使用compact或者clear方法,若是數據已經讀取完畢或者數據不要了,使用clear方法,若是已讀的數據須要保留,同時須要切換到接收數據模式,使用compat方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// 壓縮Buffer,去掉已經被讀取的數據

// 壓縮後的Buffer處於接收數據模式

public ByteBuffer compact() {

    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());

    position(remaining());

    limit(capacity());

    discardMark();

    return this;

}

 

// 清空Buffer,去掉全部數據(沒有作清理工做,是指修改位置變量)

// 清空後的Buffer處於接收數據模式

public final Buffer clear() {

    position = 0;

    limit = capacity;

    mark = -1;

    return this;

}

總結

  • Buffer是Java NIO對緩衝區的抽象
  • 除了boolean類型,其餘的基本類型都有對應的Buffer實現
  • 最經常使用的Buffer實現是ByteBuffer,具體的實現有HeapByteBuffer和DirectByteBuffer,分別對應Java堆緩衝區與對外內存緩衝區
  • HeapByteBuffer是對byte數組的封裝,方便使用
  • Buffer不是線程安全的
  • Buffer有兩種模式一種是接收數據模式,一種是輸出數據模式。新建的Buffer處於接收數據模式,使用flip方法能夠切換Buffer到輸出數據模式。使用compact或者clear方法能夠切換到接收數據模式。
相關文章
相關標籤/搜索