【Netty】Netty之ByteBuf

leesf發表於2017-05-24

一、前言

  前面已經學習了Netty中傳輸部分,現在接著學習Netty中的ByteBuf。

二、ByteBuf

  2.1 ByteBuf API

  在網路上傳輸的資料形式為Byte,Java NIO提供了ByteBuffer來作為Byte容器,該類有些複雜,而Netty使用ByteBuf作為ByteBuffer的替換方案,其提供了一個更好的API,

  Netty通過ByteBuf和ByteBufHolder兩個元件處理資料,而ByteBuf的API有如下優勢

    · 可擴充套件的使用者定義的緩衝區型別

    · 通過內建複合緩衝區型別實現透明零拷貝

    · 容量隨著需求可擴大

    · 在讀寫器模式之間切換不需要呼叫ByteBuffer的flip()方法

    · 資料讀寫使用不同的索引

    · 支援方法鏈

    · 支援引用計數

    · 支援池

  ByteBuf維護兩個不同的讀索引和寫索引,當讀ByteBuf時,readerIndex會隨著資料的讀取而不斷增加,同理,writerIndex也相同,對於空的ByteBuf而言,其readerIndex和writerIndex均初始化為0,如下圖所示

  

  而當讀取資料時,readerIndex與writerIndex相同時,表示不能再讀取資料了,否則會丟擲IndexOutOfBoundsException異常,以read或者write開頭的方法會增加相應的索引,而set和get方法則不會,可自定義ByteBuf的大小,當超過大小時將丟擲異常。

  2.2 ByteBuf使用模式

  常用的模式有如下幾種:

    · 堆緩衝。將資料儲存在JVM的堆空間中,使用backing array提供支援,這種模式在不使用池的情況下提供快速分配和釋放。

    · 直接緩衝。通過JVM的本地呼叫分配記憶體,這可避免每次呼叫本地I / O操作之前(或之後)將緩衝區的內容複製到(或從)中間緩衝區。

    · 複合緩衝。呈現多個ByteBufs的聚合檢視,可以新增或刪除ByteBuf例項,由Netty中的CompositeByteBuf提供支援,CompositeByteBuf中的ByteBuf例項包含直接或非直接的分配。

  2.3 位元組級的操作

  ByteBuf提供了很多用於修改資料的讀寫方法。

  1. 隨機訪問索引

  ByteBuf的第一個索引編號為0,最後一個編號為capacity() - 1,可使用如下程式碼讀取ByteBuf的資料  

for (int i = 0; i < buffer.capacity(); i++) {
    byte b = buffer.getByte(i);
    System.out.println((char) b);
}

  值得注意的是當有索引作為引數傳入方法而讀取資料時,並不會改變readerIndex或者writerIndex的值。

  2. 順序訪問索引

  Netty的ByteBuf有讀寫兩個索引,而JDK的ByteBuffer只有一個索引,因此需要使用flip方法進行讀寫切換,下圖展示了讀寫索引如何將ByteBuf劃分為三個區域。

  

  3. 可捨棄位元組

  可捨棄的位元組表示那些已經被讀取的資料,可通過呼叫discardReadBytes() 方法捨棄並且回收該部分空間。當呼叫了discardReadBytes方法後,其佈局如下圖所示

  

  可以看到,整個容量未變,但是此時readerIndex的值變為0,可寫的容量大小擴大了。

  4. 可讀位元組

  可讀位元組部分儲存了真實的資料。新分配的、包裝的或複製的緩衝區的readerIndex的預設值為0,以read或者skip開頭的方法操作將檢索或跳過當前readerIndex中的資料並增加讀取的位元組數,當可讀位元組已經用盡時,再進行讀取將會丟擲異常。下面程式碼將會讀取ByteBuf中的所有資料。  

ByteBuf buffer = ...;
while (buffer.isReadable()) {
    System.out.println(buffer.readByte());
}

  5. 可寫位元組

  可寫位元組部分可供寫入資料,初始化的writerIndex為0,以write開頭的將會從writerIndex開始寫入資料,並且writerIndex會增加相應的大小。當超過ByteBuf的容量時,再寫入資料時會丟擲IndexOutOfBoundException異常,如下程式碼會隨機寫入一個整形。  

ByteBuf buffer = ...;
while (buffer.writableBytes() >= 4) {
    buffer.writeInt(random.nextInt());
}

  6. 索引管理

  可通過呼叫markReaderIndex(), markWriterIndex(), resetReaderIndex(), and resetWriterIndex()方法來標記和重置readerIndex和writerIndex,也可通過呼叫readerIndex(int)、writerIndex(int) 方法來將readerIndex和writerIndex設定為指定值,也可通過呼叫clear()方法將readerIndex和writerIndex設定為0,但是並不會清空內容。

  若呼叫clear之前的佈局如下

  

  則呼叫clear之後的佈局如下

  

  clear()方法比discardReadBytes()方法效能更優,因為其不需要拷貝資料。

  7. 搜尋操作

  有多種方法確定ByteBuf指定值的索引,如使用indexOf方法,另一種更為複雜的方法是使用ByteBufProcessor作為方法的引數,如下程式碼尋找\r的索引  

ByteBuf buffer = ...;
int index = buffer.forEachByte(ByteBufProcessor.FIND_CR);  

  8. 派生緩衝區

  派生緩衝提供了ByteBuf的檢視,可通過如下方法建立檢視duplicate()、slice()、slice(int, int)、Unpooled.unmodifiableBuffer(…)、order(ByteOrder)、readSlice(int)。

  每個方法將會返回一個新的ByteBuf例項,該例項有自己的readerIndex、writerIndex、marker索引,當修改該ByteBuf例項資料時,原始資料也將被修改。如下程式碼展示了使用slice方法來建立新的ByteBuf例項用法

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf sliced = buf.slice(0, 14);
System.out.println(sliced.toString(utf8));
buf.setByte(0, (byte)'J');
assert buf.getByte(0) == sliced.getByte(0);

  其中,由於資料是共享的,對一個ByteBuf的修改對原始的ByteBuf是可見的

  下面程式碼展示了copy方法的使用  

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf copy = buf.copy(0, 14);
System.out.println(copy.toString(utf8));
buf.setByte(0, (byte)'J');
assert buf.getByte(0) != copy.getByte(0);

  copy方法會重新分配新的ByteBuf,對其的修改對原始的ByteBuf不可見。

  9. 讀/寫操作

  get和set操作讀寫指定索引的資料,而不會改變索引值。read和write操作讀寫指定索引資料,並且會改變索引的值。

  2.4 ByteBuf分配

  1. ByteBufAllocator介面

  為減少分配和重新分配記憶體的開銷,Netty使用ByteBufAllocator使用了池,其可分配任何型別的ByteBuf例項,Netty提供了ByteBufAllocator兩種型別的實現:PooledByteBufAllocator和UnpooledByteBufAllocator,前者池化ByteBuf例項以提高效能並最小化記憶體碎片,後者每次呼叫時都返回一個新的例項。Netty預設使用PooledByteBufAllocator,但也可通過ChannelConfig改變並使用不同的分配器。

  2. 非池化緩衝

  當沒有ByteBufAllocator引用時,Netty提供了Unpooled工具類,其提供了建立非池化緩衝的幫助方法,具體如下:buffer()、buffer(int initialCapacity)、buffer(int initialCapacity, int maxCapacity)、directBuffer()、directBuffer(int initialCapacity)、directBuffer(int initialCapacity, int maxCapacity)、wrappedBuffer()、copiedBuffer()等。

  3. ByteBufUtil類

  ByteBufUtil類提供了管理ByteBuf的方法,其中最有效的方法是hexdump方法,它列印ByteBuf的內容的十六進位制表示,在除錯時該方法非常有用。另一個方法是boolean equals(ByteBuf, ByteBuf) 方法,用來判斷兩個ByteBuf的相等性。

  2.5 引用計數

  引用計數是一種通過釋放由物件不再被其他物件引用的物件所持有的資源來優化記憶體使用和效能的技術,Netty在ByteBuf和ByteBufHolder的第4版中引入了引用計數,其都實現了ReferenceCounted介面。引用計數背後的思想並不複雜,主要是跟蹤指定物件的活動引用數。ReferenceCounted實現例項的初始化活動引用計數為1。只要引用計數大於0,就要保證物件不被釋放,當為0時,需要被釋放。當訪問已經被釋放的物件時會丟擲IllegalReferenceCountException異常。

三、總結

  本篇博文著重講解了ByteBuf的具體細節,以及講解了不同的緩衝區型別,其是Netty中的核心概念,可以類比JDK中的ByteBuffer進行學習,也謝謝各位園友的觀看~

相關文章