netty系列之:netty中的ByteBuf詳解

簡介

netty中用於進行信息承載和交流的類叫做ByteBuf,從名字可以看出這是Byte的緩存區,那麼ByteBuf都有哪些特性呢?一起來看看。

ByteBuf詳解

netty提供了一個io.netty.buffer的包,該包裏面定義了各種類型的ByteBuf和其衍生的類型。

netty Buffer的基礎是ByteBuf類,這是一個抽象類,其他的Buffer類基本上都是由該類衍生而得的,這個類也定義了netty整體Buffer的基調。

先來看下ByteBuf的定義:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {

ByteBuf實現了兩個接口,分別是ReferenceCounted和Comparable。Comparable是JDK自帶的接口,表示該類之間是可以進行比較的。而ReferenceCounted表示的是對象的引用統計。當一個ReferenceCounted被實例化之後,其引用count=1,每次調用retain() 方法,就會增加count,調用release() 方法又會減少count。當count減爲0之後,對象將會被釋放,如果試圖訪問被釋放過後的對象,則會報訪問異常。

如果一個對象實現了ReferenceCounted,並且這個對象裏面包含的其他對象也實現了ReferenceCounted,那麼當容器對象的count=0的時候,其內部的其他對象也會被調用release()方法進行釋放。

綜上,ByteBuf是一個可以比較的,可以計算引用次數的對象。他提供了序列或者隨機的byte訪問機制。

注意的是,雖然JDK中有自帶的ByteBuffer類,但是netty中的 ByteBuf 算是對Byte Buffer的重新實現。他們沒有關聯關係。

創建一個Buff

ByteBuf是一個抽象類,並不能直接用來實例化,雖然可以使用ByteBuf的子類進行實例化操作,但是netty並不推薦。netty推薦使用io.netty.buffer.Unpooled來進行Buff的創建工作。Unpooled是一個工具類,可以爲ByteBuf分配空間、拷貝或者封裝操作。

下面是創建幾個不同ByteBuf的例子:

   import static io.netty.buffer.Unpooled.*;

   ByteBuf heapBuffer    = buffer(128);
   ByteBuf directBuffer  = directBuffer(256);
   ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
   ByteBuf copiedBuffer  = copiedBuffer(ByteBuffer.allocate(128));

上面我們看到了4種不同的buff構建方式,普通的buff、directBuffer、wrappedBuffer和copiedBuffer。

普通的buff是固定大小的堆buff,而directBuffer是固定大小的direct buff。direct buff使用的是堆外內存,省去了數據到內核的拷貝,因此效率比普通的buff要高。

wrappedBuffer是對現有的byte arrays或者byte buffers的封裝,可以看做是一個視圖,當底層的數據發生變化的時候,Wrapped buffer中的數據也會發生變化。

Copied buffer是對現有的byte arrays、byte buffers 或者 string的深拷貝,所以它和wrappedBuffer是不同的,Copied buffer和原數據之間並不共享數據。

隨機訪問Buff

熟悉集合的朋友應該都知道,要想隨機訪問某個集合,一定是通過index來訪問的,ByteBuf也一樣,可以通過capacity或得其容量,然後通過getByte方法隨機訪問其中的byte,如下所示:

        //隨機訪問
        ByteBuf buffer = heapBuffer;
        for (int i = 0; i < buffer.capacity(); i ++) {
            byte b = buffer.getByte(i);
            System.out.println((char) b);
        }

序列讀寫

讀寫要比訪問複雜一點,ByteBuf 提供了兩個index用來定位讀和寫的位置,分別是readerIndex 和 writerIndex ,兩個index分別控制讀和寫的位置。

下圖顯示的一個buffer被分成了三部分,分別是可廢棄的bytes、可讀的bytes和可寫的bytes。

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

上圖還表明了readerIndex、writerIndex和capacity的大小關係。

其中readable bytes是真正的內容,可以通過調用read* 或者skip* 的方法來進行訪問或者跳過,調用這些方法的時候,readerIndex會同步增加,如果超出了readable bytes的範圍,則會拋出IndexOutOfBoundsException。默認情況下readerIndex=0。

下面是一個遍歷readable bytes的例子:

        //遍歷readable bytes
        while (directBuffer.isReadable()) {
            System.out.println(directBuffer.readByte());
        }

首先通過判斷是否是readable來決定是否調用readByte方法。

Writable bytes是一個未確定的區域,等待被填充。可以通過調用write*方法對其操作,同時writerIndex 會同步更新,同樣的,如果空間不夠的話,也會拋出IndexOutOfBoundsException。默認情況下 新分配的writerIndex =0 ,而wrapped 或者copied buffer的writerIndex=buf的capacity。

下面是一個使用writable Byte的例子:

        //寫入writable bytes
        while (wrappedBuffer.maxWritableBytes() >= 4) {
            wrappedBuffer.writeInt(new Random().nextInt());
        }

Discardable bytes是已經被讀取過的bytes,初始情況下它的值=0,每當readerIndex右移的時候,Discardable bytes的空間就會增加。如果想要完全刪除或重置Discardable bytes,則可以調用discardReadBytes()方法,該方法會將Discardable bytes空間刪除,將多餘的空間放到writable bytes中,如下所示:

調用 discardReadBytes() 之前:

    +-------------------+------------------+------------------+
    | discardable bytes |  readable bytes  |  writable bytes  |
    +-------------------+------------------+------------------+
    |                   |                  |                  |
    0      <=      readerIndex   <=   writerIndex    <=    capacity


調用 discardReadBytes()之後:

    +------------------+--------------------------------------+
    |  readable bytes  |    writable bytes (got more space)   |
    +------------------+--------------------------------------+
    |                  |                                      |

readerIndex (0) <= writerIndex (decreased) <= capacity

注意,雖然writable bytes變多了,但是其內容是不可控的,並不能保證裏面的內容是空的或者不變。

調用clear()方法會將readerIndex 和 writerIndex 清零,注意clear方法只會設置readerIndex 和 writerIndex 的值,並不會清空content,看下面的示意圖:

調用 clear()之前:

    +-------------------+------------------+------------------+
    | discardable bytes |  readable bytes  |  writable bytes  |
    +-------------------+------------------+------------------+
    |                   |                  |                  |
    0      <=      readerIndex   <=   writerIndex    <=    capacity


調用 clear()之後:

    +---------------------------------------------------------+
    |             writable bytes (got more space)             |
    +---------------------------------------------------------+
    |                                                         |
    0 = readerIndex = writerIndex            <=            capacity

搜索

ByteBuf提供了單個byte的搜索功能,如 indexOf(int, int, byte) 和 bytesBefore(int, int, byte)兩個方法。

如果是要對ByteBuf遍歷進行搜索處理的話,可以使用 forEachByte(int, int, ByteProcessor),這個方法接收一個ByteProcessor用於進行復雜的處理。

其他衍生buffer方法

ByteBuf還提供了很多方法用來創建衍生的buffer,如下所示:

duplicate()
slice()
slice(int, int)
readSlice(int)
retainedDuplicate()
retainedSlice()
retainedSlice(int, int)
readRetainedSlice(int)

要注意的是,這些buf是建立在現有buf基礎上的衍生品,他們的底層內容是一樣的,只有readerIndex, writerIndex 和做標記的index不一樣。所以他們和原buf是有共享數據的。如果你希望的是新建一個全新的buffer,那麼可以使用copy()方法或者前面提到的Unpooled.copiedBuffer。

在前面小節中,我們講到ByteBuf是一個ReferenceCounted,這個特徵在衍生buf中就用到了。我們知道調用retain() 方法的時候,引用count會增加,但是對於 duplicate(), slice(), slice(int, int) 和 readSlice(int) 這些方法來說,雖然他們也是引用,但是沒有調用retain()方法,這樣原始數據會在任意一個Buf調用release()方法之後被回收。

如果不想有上面的副作用,那麼可以將方法替換成retainedDuplicate(), retainedSlice(), retainedSlice(int, int) 和 readRetainedSlice(int) ,這些方法會調用retain()方法以增加一個引用。

和現有JDK類型的轉換

之前提到了ByteBuf 是對ByteBuffer的重寫,他們是不同的實現。雖然這兩個不同,但是不妨礙將ByteBuf轉換ByteBuffer。

當然,最簡單的轉換是把ByteBuf轉換成byte數組byte[]。要想轉換成byte數組,可以先調用hasArray() 進行判斷,然後再調用array()方法進行轉換。

同樣的ByteBuf還可以轉換成爲ByteBuffer ,可以先調用 nioBufferCount()判斷能夠轉換成爲 ByteBuffers的個數,再調用nioBuffer() 進行轉換。

返回的ByteBuffer是對現有buf的共享或者複製,對返回之後buffer的position和limit修改不會影響到原buf。

最後,使用toString(Charset) 方法可以將ByteBuf轉換成爲String。

總結

ByteBuf是netty的底層基礎,是傳輸數據的承載對象,深入理解ByteBuf就可以搞懂netty的設計思想,非常不錯。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/02-netty-bytebuf/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注我的公衆號:「程序那些事」,懂技術,更懂你!

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