天天看點

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

Netty學習之旅------第3篇---ByteBuf解讀

  • 1. JDK ByteBuffer
    • 1.1 長度固定
    • 1.2 操作複雜
    • 1.3 API 不豐富
  • 2. ByteBuf 的設計
    • 2.1 引入writeIndex 和 readerIndex
    • 2.2 擴容
  • 3. ByteBuf 功能介紹
    • 3.1 順序讀寫
      • 3.1.2 順序讀
      • 3.1.2 順序寫
    • 3.2 随機讀寫
      • 3.2.1 随機讀
      • 3.2.2 随機寫
    • 3.3 readerIndex和writeIndex
    • 3.4 discardable bytes、readable bytes 和 writeable bytes
    • 3.5 clear mark 和 reset
      • 3.5.1 clear 方法
      • 3.5.2 mark 方法和reset 方法
    • 3.6 轉成 JDK 的ByteBuffer
  • 4 總結

溫馨提示:内容局限于本人的了解,如果有錯誤,請指正,謝謝!

學習目标:

(1)了解JDK 的ByteBuffer 的局限性

(2)了解ByteBuf的設計思想

(3)了解ByteBuf的API

1. JDK ByteBuffer

前面章節,我們學習了NIO的緩沖區,當我們要進行資料傳輸的時候,往往需要用到緩沖區,常用的緩沖區就是JDK 的 ByteBuffer,也還有其它的緩沖區,針對不同的資料類型,都有不同的緩沖區。但NIO 程式設計很複雜,JDK 的ByteBuffer 也有其局限性,總結如下:

1.1 長度固定

在初始緩沖區的時候,需要指定長度,長度一旦固定,将不能修改。不能縮容和擴容,如果存儲對象,錯誤的預估了對象的大小,将導緻索引越界異常。

1.2 操作複雜

JDK ByteBuffer的API使用起來不友善,比如要把一個緩沖區由寫狀态變成讀狀态,還需要通過flip方法,翻轉緩沖區。開發人員必須小心的使用這些API,極容易出錯。下面通過案例來看下flip方法:在緩沖區中,讀寫是通過position和limit來控制的,當寫入的時候,初始的position=0,limit=可寫的最大長度,當寫入一個内容,

+---------------------------------------------------------+
       |                                                         |  
       +---------------------------------------------------------+
       |                                                         |    
       0=position                                               limit ==capacity
           

當我寫入一個字元’H’,就改變成下圖了

+---------------------------------------------------------+
       |H|                                                       |  
       +-+-------------------------------------------------------+
       |                                                         |    
       0  < position                                       limit ==capacity
           

此時position=1了。如果我要讀取這個緩沖區中的内容,需要做翻轉,否則讀取到的就是position到limit直接的空資料。調用flip後

+---------------------------------------------------------+
       |H|                                                       |  
       +-+-------------------------------------------------------+
       |                                                         |    
       0=position limit=1                                       capacity
           

翻轉的操作其實就是做了:limit = position;position=0 ,這樣讀取,position到limit的内容,才是我們想要的内容。

1.3 API 不豐富

很多的api也不支援,如果要實作某些功能,還需要開發人員自己程式設計實作

2. ByteBuf 的設計

注意這個是netty的類了,類名不一樣。這個是對于JDK ByteBuffer 的局限性來設計的。那它是怎麼縮容和擴容,和flip類似的操作的呢?帶着疑問,繼續往下看。

2.1 引入writeIndex 和 readerIndex

為了避免複雜的position和limit的關系,這裡引入了writeIndex 和 readerIndex,看名字就知道,這是一個讀的索引和寫的索引。内部的結構是這樣的。

*      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      |                   |     (CONTENT)    |                  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity
           

這裡的區域簡單介紹下:

discardable bytes:表示已經讀取,可以丢棄的區域。

readable bytes:是還未讀取,已存内容的區域。

writable bytes: 是可寫入的内容的區域

當往ByteBuf 寫入的時候,writeIndex增加,讀取的時候會推動readerIndex增加,比如我讀取一個調用writeInt(int value)方法,寫入一個int 類型的值,writeIndex會在原理基礎上增加int的位元組長度,即writeIndex=writeIndex+4;如果調用readInt(),會在readerIndex的基礎上減少int的位元組長度,即readerIndex=readerIndex-4,其它的API也類似。

這是裡面一個永恒的關系:

0      <=      readerIndex   <=   writerIndex    <=    capacity
           

2.2 擴容

JDK的ByteBuffer 容量是固定的,如果存儲的資料超過容量,會抛出異常,是以ByteBuf中引入了擴容機制,在寫入的時候,進行容量判斷,如果容納不下,則按照一定比例進行擴容。

3. ByteBuf 功能介紹

3.1 順序讀寫

3.1.2 順序讀

順序讀是read開頭的方法,相當于JDK ByteBuffer 中的get操作。具體的API 如下圖所示

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

比如

readByte就是從readerIndex開始讀位元組,讀完readerIndex++,傳回byte類型;

readInt就是從readerIndex開始讀整型,讀完readerIndex=readerIndex+4,傳回整形。

3.1.2 順序寫

順序讀是write開頭的方法,相當于JDK ByteBuffer 中的put操作。具體的API 如下圖所示

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

比如

writeBytes(ByteBuf src) 是從writeIndex開始寫,把src中的資料都寫入到目前緩沖區,寫完writeIndex=writeIndex+readableBytes,如果src緩沖區的可讀位元組數大于目前緩沖區可寫的位元組數,将抛出IndexOutOfBoundsException異常。

writeZero(int length) 是将目前緩沖區填充為NUL(0x00),length是填充的長度,填充後writeIndex=writeIndex+length

3.2 随機讀寫

随機讀寫的API都會判斷index是否合法,如果不合法,直接抛出IndexOutOfBoundsException異常。

3.2.1 随機讀

以get開頭的方法,都提供了根據index來讀操作的API

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

3.2.2 随機寫

以set開頭的方法,都提供了根據index來寫操作的API

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

3.3 readerIndex和writeIndex

readerIndex 維護了讀索引,往緩沖區讀資料的時候,readerIndex會根據你讀取的長度增加。

writeIndex 維護了寫索引,往緩沖區寫資料的時候,writeIndex會根據你寫入的長度增加,引入這2個,大大降低了緩沖區的複雜度。

0 <= readerIndex <= writerIndex <= capacity

0到readerIndex 表示已經讀取過的緩沖區

readerIndex到writerIndex表示未讀取過的緩沖區

writerIndex到capacity表示還可以寫入的緩沖區

3.4 discardable bytes、readable bytes 和 writeable bytes

discardable bytes 表示已經讀取過,可以釋放的區域。但是此方法是以時間換空間的操作。原因是調用discardable byte 會導緻位元組數組的記憶體複制。

例如:下圖展示了寫入了一部分資料的緩沖區,

調用前

*      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      |                   |     (CONTENT)    |                  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity
           

調用discardReadBytes方法後:

*      +-------------------+-------------------------------------+
 *      |  readable bytes   |    writable bytes                   |
 *      |   (CONTENT)       |                                     |
 *      +-------------------+-------------------------------------+
 *      |                   |                                     |
 *      readerIndex=0   <=   writerIndex       <=             capacity
           

等于把discardable bytes區域的釋放掉,然後把readable bytes移動到discardable bytes釋放出來的區域,然後可寫入的區域就變大了。是以discardable bytes要慎用。

3.5 clear mark 和 reset

3.5.1 clear 方法

clear方法是把readerIndex和writeIndex重置為0,并不會情況緩沖區内容,調用後全部區域變成可寫狀态。

*      +---------------------------------------------------------+
 *      |                    writable bytes                       |
 *      |                                                         |
 *      +---------------------------------------------------------+
 *      |                                                         |
 *      0=readerIndex=writerIndex                            capacity
           

3.5.2 mark 方法和reset 方法

mark方法其實就是備份readerIndex、writerIndex的值,reset就是還原到mark備份的值,例如有些操作需要復原,就可以根據mark标記過的位置,調用reset復原,在ByteBuf中,對應的有2個mark方法和2個reset方法,分别是:markReaderIndex和markWriterIndex、resetReaderIndex和resetWriterIndex。

3.6 轉成 JDK 的ByteBuffer

netty 提供了nioBuffer相關方法轉成NIO 的ByteBuffer

Netty學習之旅------第3篇---ByteBuf解讀1. JDK ByteBuffer2. ByteBuf 的設計3. ByteBuf 功能介紹4 總結

4 總結

通過對ByteBuf的分析,我們知道其實ByteBuf是在ByteBuffer的局限性而設計的。簡化了開發人員的學習成本,并且定義了頂層的api接口,後續将會對其一些實作類進行具體的分析。