Netty字節(jié)數(shù)據(jù)的容器ByteBuf

2022-04-21 09:12 更新

既然所有的網(wǎng)絡(luò)通信都是要基于底層的字節(jié)流來傳輸,那么傳輸所使用的數(shù)據(jù)接口就要求是效率高得、使用方便的而且容易使用的,NettyByteBuf更好能夠達(dá)到這些要求。

ByteBuf 是一個(gè)已經(jīng)經(jīng)過優(yōu)化的很好使用的數(shù)據(jù)容器,字節(jié)數(shù)據(jù)可以有效的被添加到 ByteBuf 中或者也可以從 ByteBuf 中直接獲取數(shù)據(jù)。ByteBuf中有兩個(gè)索引:一個(gè)用來讀,一個(gè)用來寫。這兩個(gè)索引達(dá)到了便于操作的目的。我們可以按順序的讀取數(shù)據(jù),也可以通過調(diào)整讀取數(shù)據(jù)的索引或者直接將讀取位置索引作為參數(shù)傳遞給get方法來重復(fù)讀取數(shù)據(jù)。

ByteBuf 的工作原理

寫入數(shù)據(jù)到 ByteBuf 后,writerIndex(寫入索引)增加寫入的字節(jié)數(shù)。讀取字節(jié)后,readerIndex(讀取索引)也增加讀取出的字節(jié)數(shù)。你可以讀取字節(jié),直到寫入索引和讀取索引處在相同的位置。此時(shí)ByteBuf不可讀,所以下一次讀操作將會拋出 IndexOutOfBoundsException,就像讀取數(shù)組時(shí)越位一樣。

調(diào)用 ByteBuf 的以 "read" 或 "write" 開頭的任何方法都將自動增加相應(yīng)的索引。另一方面,"set" 、 "get"操作字節(jié)將不會移動索引位置,它們只會在指定的相對位置上操作字節(jié)。

可以給ByteBuf指定一個(gè)最大容量值,這個(gè)值限制著ByteBuf的容量。任何嘗試將寫入超過這個(gè)值的數(shù)據(jù)的行為都將導(dǎo)致拋出異常。ByteBuf 的默認(rèn)最大容量限制是 Integer.MAX_VALUE。

ByteBuf 類似于一個(gè)字節(jié)數(shù)組,最大的區(qū)別是讀和寫的索引可以用來控制對緩沖區(qū)數(shù)據(jù)的訪問。下圖顯示了一個(gè)容量為16的空的 ByteBuf 的布局和狀態(tài),writerIndex 和 readerIndex 都在索引位置 0 :

11385343fbf2b2119fb390fced3374300dd78e19

ByteBuf 使用模式

HEAP BUFFER(堆緩沖區(qū))

最常用的模式是 ByteBuf 將數(shù)據(jù)存儲在 JVM 的堆空間,這是通過將數(shù)據(jù)存儲在數(shù)組的實(shí)現(xiàn)。堆緩沖區(qū)可以快速分配,當(dāng)不使用時(shí)也可以快速釋放。它還提供了直接訪問數(shù)組的方法,通過 ByteBuf.array() 來獲取 byte[]數(shù)據(jù)。 這種方法,正如清單5.1中所示的那樣,是非常適合用來處理遺留數(shù)據(jù)的。

Listing 5.1 Backing array

ByteBuf heapBuf = ...;
if (heapBuf.hasArray()) {                //1
    byte[] array = heapBuf.array();        //2
    int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();                //3
    int length = heapBuf.readableBytes();//4
    handleArray(array, offset, length); //5
}

1.檢查 ByteBuf 是否有支持?jǐn)?shù)組。

2.如果有的話,得到引用數(shù)組。

3.計(jì)算第一字節(jié)的偏移量。

4.獲取可讀的字節(jié)數(shù)。

5.使用數(shù)組,偏移量和長度作為調(diào)用方法的參數(shù)。

注意:

  • 訪問非堆緩沖區(qū) ByteBuf 的數(shù)組會導(dǎo)致UnsupportedOperationException, 可以使用 ByteBuf.hasArray()來檢查是否支持訪問數(shù)組。
  • 這個(gè)用法與 JDK 的 ByteBuffer 類似

DIRECT BUFFER(直接緩沖區(qū))

“直接緩沖區(qū)”是另一個(gè) ByteBuf 模式。對象的所有內(nèi)存分配發(fā)生在 堆,對不對?好吧,并非總是如此。在 JDK1.4 中被引入 NIO 的ByteBuffer 類允許 JVM 通過本地方法調(diào)用分配內(nèi)存,其目的是

  • 通過免去中間交換的內(nèi)存拷貝, 提升IO處理速度; 直接緩沖區(qū)的內(nèi)容可以駐留在垃圾回收掃描的堆區(qū)以外。
  • DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的內(nèi)存, GC對此”無能為力”,也就意味著規(guī)避了在高負(fù)載下頻繁的GC過程對應(yīng)用線程的中斷影響.(詳見http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html.)

這就解釋了為什么“直接緩沖區(qū)”對于那些通過 socket 實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)膽?yīng)用來說,是一種非常理想的方式。如果你的數(shù)據(jù)是存放在堆中分配的緩沖區(qū),那么實(shí)際上,在通過 socket 發(fā)送數(shù)據(jù)之前,JVM 需要將先數(shù)據(jù)復(fù)制到直接緩沖區(qū)。

但是直接緩沖區(qū)的缺點(diǎn)是在內(nèi)存空間的分配和釋放上比堆緩沖區(qū)更復(fù)雜,另外一個(gè)缺點(diǎn)是如果要將數(shù)據(jù)傳遞給遺留代碼處理,因?yàn)閿?shù)據(jù)不是在堆上,你可能不得不作出一個(gè)副本,如下:

Listing 5.2 Direct buffer data access

ByteBuf directBuf = ...
if (!directBuf.hasArray()) {            //1
    int length = directBuf.readableBytes();//2
    byte[] array = new byte[length];    //3
    directBuf.getBytes(directBuf.readerIndex(), array);        //4    
    handleArray(array, 0, length);  //5
}

1.檢查 ByteBuf 是不是由數(shù)組支持。如果不是,這是一個(gè)直接緩沖區(qū)。

2.獲取可讀的字節(jié)數(shù)

3.分配一個(gè)新的數(shù)組來保存字節(jié)

4.字節(jié)復(fù)制到數(shù)組

5.將數(shù)組,偏移量和長度作為參數(shù)調(diào)用某些處理方法

顯然,這比使用數(shù)組要多做一些工作。因此,如果你事前就知道容器里的數(shù)據(jù)將作為一個(gè)數(shù)組被訪問,你可能更愿意使用堆內(nèi)存。

COMPOSITE BUFFER(復(fù)合緩沖區(qū))

最后一種模式是復(fù)合緩沖區(qū),我們可以創(chuàng)建多個(gè)不同的 ByteBuf,然后提供一個(gè)這些 ByteBuf 組合的視圖。復(fù)合緩沖區(qū)就像一個(gè)列表,我們可以動態(tài)的添加和刪除其中的 ByteBuf,JDK 的 ByteBuffer 沒有這樣的功能。

Netty 提供了 ByteBuf 的子類 CompositeByteBuf 類來處理復(fù)合緩沖區(qū),CompositeByteBuf 只是一個(gè)視圖。

警告

CompositeByteBuf.hasArray() 總是返回 false,因?yàn)樗赡芗劝丫彌_區(qū),也包含直接緩沖區(qū)

例如,一條消息由 header 和 body 兩部分組成,將 header 和 body 組裝成一條消息發(fā)送出去,可能 body 相同,只是 header 不同,使用CompositeByteBuf 就不用每次都重新分配一個(gè)新的緩沖區(qū)。下圖顯示CompositeByteBuf 組成 header 和 body:

Figure 5.2 CompositeByteBuf holding a header and body

Figure%205

下面代碼顯示了使用 JDK 的 ByteBuffer 的一個(gè)實(shí)現(xiàn)。兩個(gè) ByteBuffer 的數(shù)組創(chuàng)建保存消息的組件,第三個(gè)創(chuàng)建用于保存所有數(shù)據(jù)的副本。

Listing 5.3 Composite buffer pattern using ByteBuffer

// 使用數(shù)組保存消息的各個(gè)部分
ByteBuffer[] message = { header, body };

// 使用副本來合并這兩個(gè)部分
ByteBuffer message2 = ByteBuffer.allocate(
        header.remaining() + body.remaining());
message2.put(header);
message2.put(body);
message2.flip();

這種做法顯然是低效的;分配和復(fù)制操作不是最優(yōu)的方法,操縱數(shù)組使代碼顯得很笨拙。

下面看使用 CompositeByteBuf 的改進(jìn)版本

Listing 5.4 Composite buffer pattern using CompositeByteBuf

CompositeByteBuf messageBuf = ...;
ByteBuf headerBuf = ...; // 可以支持或直接
ByteBuf bodyBuf = ...; // 可以支持或直接
messageBuf.addComponents(headerBuf, bodyBuf);
// ....
messageBuf.removeComponent(0); // 移除頭    //2

for (int i = 0; i < messageBuf.numComponents(); i++) {                        //3
    System.out.println(messageBuf.component(i).toString());
}

1.追加 ByteBuf 實(shí)例的 CompositeByteBuf

2.刪除 索引1的 ByteBuf

3.遍歷所有 ByteBuf 實(shí)例。

清單5.4 所示,你可以簡單地把 CompositeByteBuf 當(dāng)作一個(gè)可迭代遍歷的容器。 CompositeByteBuf 不允許訪問其內(nèi)部可能存在的支持?jǐn)?shù)組,也不允許直接訪問數(shù)據(jù),這一點(diǎn)類似于直接緩沖區(qū)模式,如圖5.5所示。

Listing 5.5 Access data

CompositeByteBuf compBuf = ...;
int length = compBuf.readableBytes();    //1
byte[] array = new byte[length];        //2
compBuf.getBytes(compBuf.readerIndex(), array);    //3
handleArray(array, 0, length);    //4

1.得到的可讀的字節(jié)數(shù)。

2.分配一個(gè)新的數(shù)組,數(shù)組長度為可讀字節(jié)長度。

3.讀取字節(jié)到數(shù)組

4.使用數(shù)組,把偏移量和長度作為參數(shù)

Netty 嘗試使用 CompositeByteBuf 優(yōu)化 socket I/O 操作,消除 原生 JDK 中可能存在的的性能低和內(nèi)存消耗問題。雖然這是在Netty 的核心代碼中進(jìn)行的優(yōu)化,并且是不對外暴露的,但是作為開發(fā)者還是應(yīng)該意識到其影響。

CompositeByteBuf API

CompositeByteBuf 提供了大量的附加功能超出了它所繼承的 ByteBuf。請參閱的 Netty 的 Javadoc 文檔 API。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號