天天看點

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

正如我們先前所指出的,網絡資料的基本機關永遠是 byte(位元組)。Java NIO 提供 ByteBuffer 作為位元組的容器,但這個類是過于複雜,有點 難以使用。

Netty 中 ByteBuffer 替代是 ByteBuf,一個強大的實作,解決 JDK 的 API 的限制,以及為網絡應用程式開發者一個更好的工具。 但 ByteBuf 并不僅僅暴露操作一個位元組序列的方法;這也是專門的Netty 的 ChannelPipeline 的語義設計。

在本章中,我們會說明相比于 JDK 的 API,ByteBuf 所提供的卓越的功能和靈活性。這也将使我們能夠更好地了解了 Netty 的資料處理。

Buffer API

主要包括

  • ByteBuf
  • ByteBufHolder

Netty 使用 reference-counting(引用計數)來判斷何時可以釋放 ByteBuf 或 ByteBufHolder 和其他相關資源,進而可以利用池和其他技巧來提高性能和降低記憶體的消耗。這一點上不需要開發人員做任何事情,但是在開發 Netty 應用程式時,尤其是使用 ByteBuf 和 ByteBufHolder 時,你應該盡可能早地釋放池資源。 Netty 緩沖 API 提供了幾個優勢:

  • 可以自定義緩沖類型
  • 通過一個内置的複合緩沖類型實作零拷貝
  • 擴充性好,比如 StringBuilder
  • 不需要調用 flip() 來切換讀/寫模式
  • 讀取和寫入索引分開
  • 方法鍊
  • 引用計數
  • Pooling(池)

ByteBuf - 位元組資料的容器

因為所有的網絡通信最終都是基于底層的位元組流傳輸,是以一個高效、友善、易用的資料接口是必要的,而 Netty 的 ByteBuf 滿足這些需求。

ByteBuf 是一個很好的經過優化的資料容器,我們可以将位元組資料有效的添加到 ByteBuf 中或從 ByteBuf 中擷取資料。ByteBuf 有2部分:一個用于讀,一個用于寫。我們可以按順序的讀取資料,也可以通過調整讀取資料的索引或者直接将讀取位置索引作為參數傳遞給get方法來重複讀取資料。

ByteBuf 如何在工作?

寫入資料到 ByteBuf 後,writerIndex(寫入索引)增加。開始讀位元組後,readerIndex(讀取索引)增加。你可以讀取位元組,直到寫入索引和讀取索引處在相同的位置,ByteBuf 變為不可讀。當通路資料超過數組的最後位,則會抛出 IndexOutOfBoundsException。

調用 ByteBuf 的 "read" 或 "write" 開頭的任何方法都會提升 相應的索引。另一方面,"set" 、 "get"操作位元組将不會移動索引位置;他們隻會操作相關的通過參數傳入方法的相對索引。

可以給ByteBuf指定一個最大容量值,這個值限制着ByteBuf的容量。任何嘗試将寫入索引超過這個值的行為都将導緻抛出異常。ByteBuf 的預設最大容量限制是 Integer.MAX_VALUE。

ByteBuf 類似于一個位元組數組,最大的差別是讀和寫的索引可以用來控制對緩沖區資料的通路。下圖顯示了一個容量為16的空的 ByteBuf 的布局和狀态,writerIndex 和 readerIndex 都在索引位置 0 :

Figure 5.1 A 16-byte ByteBuf with its indices set to 0

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

ByteBuf 使用模式

HEAP BUFFER(堆緩沖區)

最常用的模式是 ByteBuf 将資料存儲在 JVM 的堆空間,這是通過将資料存儲在數組的實作。堆緩沖區可以快速配置設定,當不使用時也可以快速釋放。它還提供了直接通路數組的方法,通過 ByteBuf.array() 來擷取 byte[]資料。 這種方法,正如清單5.1中所示的那樣,是非常适合用來處理遺留資料的。

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 是否有支援數組。

2.如果有的話,得到引用數組。

3.計算第一位元組的偏移量。

4.擷取可讀的位元組數。

5.使用數組,偏移量和長度作為調用方法的參數。

注意:

  • 通路非堆緩沖區 ByteBuf 的數組會導緻UnsupportedOperationException, 可以使用 ByteBuf.hasArray()來檢查是否支援通路數組。
  • 這個用法與 JDK 的 ByteBuffer 類似

DIRECT BUFFER(直接緩沖區)

“直接緩沖區”是另一個 ByteBuf 模式。對象的所有記憶體配置設定發生在 堆,對不對?好吧,并非總是如此。在 JDK1.4 中被引入 NIO 的ByteBuffer 類允許 JVM 通過本地方法調用配置設定記憶體,其目的是

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

這就解釋了為什麼“直接緩沖區”對于那些通過 socket 實作資料傳輸的應用來說,是一種非常理想的方式。如果你的資料是存放在堆中配置設定的緩沖區,那麼實際上,在通過 socket 發送資料之前,JVM 需要将先資料複制到直接緩沖區。

但是直接緩沖區的缺點是在記憶體空間的配置設定和釋放上比堆緩沖區更複雜,另外一個缺點是如果要将資料傳遞給遺留代碼處理,因為資料不是在堆上,你可能不得不作出一個副本,如下:

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 是不是由數組支援。如果不是,這是一個直接緩沖區。

2.擷取可讀的位元組數

3.配置設定一個新的數組來儲存位元組

4.位元組複制到數組

5.将數組,偏移量和長度作為參數調用某些處理方法

顯然,這比使用數組要多做一些工作。是以,如果你事前就知道容器裡的資料将作為一個數組被通路,你可能更願意使用堆記憶體。

COMPOSITE BUFFER(複合緩沖區)

最後一種模式是複合緩沖區,我們可以建立多個不同的 ByteBuf,然後提供一個這些 ByteBuf 組合的視圖。複合緩沖區就像一個清單,我們可以動态的添加和删除其中的 ByteBuf,JDK 的 ByteBuffer 沒有這樣的功能。

Netty 提供了 ByteBuf 的子類 CompositeByteBuf 類來處理複合緩沖區,CompositeByteBuf 隻是一個視圖。

警告

CompositeByteBuf.hasArray() 總是傳回 false,因為它可能既包含堆緩沖區,也包含直接緩沖區

例如,一條消息由 header 和 body 兩部分組成,将 header 和 body 組裝成一條消息發送出去,可能 body 相同,隻是 header 不同,使用CompositeByteBuf 就不用每次都重新配置設定一個新的緩沖區。下圖顯示CompositeByteBuf 組成 header 和 body:

Figure 5.2 CompositeByteBuf holding a header and body

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

下面代碼顯示了使用 JDK 的 ByteBuffer 的一個實作。兩個 ByteBuffer 的數組建立儲存消息的元件,第三個建立用于儲存所有資料的副本。

Listing 5.3 Composite buffer pattern using ByteBuffer

// 使用數組儲存消息的各個部分
ByteBuffer[] message = { header, body };

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

這種做法顯然是低效的;配置設定和複制操作不是最優的方法,操縱數組使代碼顯得很笨拙。

下面看使用 CompositeByteBuf 的改進版本

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 執行個體的 CompositeByteBuf

2.删除 索引1的 ByteBuf

3.周遊所有 ByteBuf 執行個體。

清單5.4 所示,你可以簡單地把 CompositeByteBuf 當作一個可疊代周遊的容器。 CompositeByteBuf 不允許通路其内部可能存在的支援數組,也不允許直接通路資料,這一點類似于直接緩沖區模式,如圖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.得到的可讀的位元組數。

2.配置設定一個新的數組,數組長度為可讀位元組長度。

3.讀取位元組到數組

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

Netty 嘗試使用 CompositeByteBuf 優化 socket I/O 操作,消除 原生 JDK 中可能存在的的性能低和記憶體消耗問題。雖然這是在Netty 的核心代碼中進行的優化,并且是不對外暴露的,但是作為開發者還是應該意識到其影響。

CompositeByteBuf API

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

位元組級别的操作

除了基本的讀寫操作, ByteBuf 還提供了它所包含的資料的修改方法。

随機通路索引

ByteBuf 使用zero-based 的 indexing(從0開始的索引),第一個位元組的索引是 0,最後一個位元組的索引是 ByteBuf 的 capacity - 1,下面代碼是周遊 ByteBuf 的所有位元組:

Listing 5.6 Access data

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

注意通過索引通路時不會推進 readerIndex (讀索引)和 writerIndex(寫索引),我們可以通過 ByteBuf 的 readerIndex(index) 或 writerIndex(index) 來分别推進讀索引或寫索引

順序通路索引

ByteBuf 提供兩個指針變量支付讀和寫操作,讀操作是使用 readerIndex(),寫操作時使用 writerIndex()。這和JDK的ByteBuffer不同,ByteBuffer隻有一個方法來設定索引,是以需要使用 flip() 方法來切換讀和寫模式。

ByteBuf 一定符合:0 <= readerIndex <= writerIndex <= capacity。

Figure 5.3 ByteBuf internal segmentation

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

1.位元組,可以被丢棄,因為它們已經被讀

2.還沒有被讀的位元組是:“readable bytes(可讀位元組)”

3.空間可加入多個位元組的是:“writeable bytes(寫位元組)”

可丢棄位元組的位元組

标有“可丢棄位元組”的段包含已經被讀取的位元組。他們可以被丢棄,通過調用discardReadBytes() 來回收空間。這個段的初始大小存儲在readerIndex,為 0,當“read”操作被執行時遞增(“get”操作不會移動 readerIndex)。

圖5.4示出了在 圖5.3 中的緩沖區中調用 discardReadBytes() 所示的結果。你可以看到,在丢棄位元組段的空間已變得可用寫。需要注意的是不能保證對可寫的段之後的内容在 discardReadBytes() 方法之後已經被調用。

Figure 5.4 ByteBuf after discarding read bytes.

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

1.位元組尚未被讀出(readerIndex 現在 0)。 2.可用的空間,由于空間被回收而增大。

ByteBuf.discardReadBytes() 可以用來清空 ByteBuf 中已讀取的資料,進而使 ByteBuf 有多餘的空間容納新的資料,但是discardReadBytes() 可能會涉及記憶體複制,因為它需要移動 ByteBuf 中可讀的位元組到開始位置,這樣的操作會影響性能,一般在需要馬上釋放記憶體的時候使用收益會比較大。

可讀位元組

ByteBuf 的“可讀位元組”分段存儲的是實際資料。新配置設定,包裝,或複制的緩沖區的 readerIndex 的預設值為 0 。任何操作,其名稱以 "read" 或 "skip" 開頭的都将檢索或跳過該資料在目前 readerIndex ,并且通過讀取的位元組數來遞增。

如果所謂的讀操作是一個指定 ByteBuf 參數作為寫入的對象,并且沒有一個目标索引參數,目标緩沖區的 writerIndex 也會增加了。例如:

readBytes(ByteBuf dest);
           

如果試圖從緩沖器讀取已經用盡的可讀的位元組,則抛出IndexOutOfBoundsException。清單5.8顯示了如何讀取所有可讀位元組。

Listing 5.7 Read all data

//周遊緩沖區的可讀位元組
ByteBuf buffer= ...;
while (buffer.isReadable()) {
    System.out.println(buffer.readByte());
}
           

這段是未定義内容的地方,準備好寫。一個新配置設定的緩沖區的 writerIndex 的預設值是 0 。任何操作,其名稱 "write"開頭的操作在目前的 writerIndex 寫入資料時,遞增位元組寫入的數量。如果寫操作的目标也是 ByteBuf ,且未指定源索引,則源緩沖區的 readerIndex 将增加相同的量。例如:

writeBytes(ByteBuf dest);
           

如果試圖寫入超出目标的容量,則抛出 IndexOutOfBoundException。

下面的例子展示了填充随機整數到緩沖區中,直到耗盡空間。該方法writableBytes() 被用在這裡确定是否存在足夠的緩沖空間。

Listing 5.8 Write data

//填充随機整數到緩沖區中
ByteBuf buffer = ...;
while (buffer.writableBytes() >= 4) {
    buffer.writeInt(random.nextInt());
}
           

索引管理

在 JDK 的 InputStream 定義了 mark(int readlimit) 和 reset()方法。這些是分别用來标記流中的目前位置和複位流到該位置。

同樣,您可以設定和重新定位ByteBuf readerIndex 和 writerIndex 通過調用 markReaderIndex(), markWriterIndex(), resetReaderIndex() 和 resetWriterIndex()。這些類似于InputStream 的調用,所不同的是,沒有 readlimit 參數來指定當标志變為無效。

您也可以通過調用 readerIndex(int) 或 writerIndex(int) 将名額移動到指定的位置。在嘗試任何無效位置上設定一個索引将導緻 IndexOutOfBoundsException 異常。

調用 clear() 可以同時設定 readerIndex 和 writerIndex 為 0。注意,這不會清除記憶體中的内容。讓我們看看它是如何工作的。 (圖5.5圖重複5.3 )

Figure 5.5 Before clear() is called

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

調用之前,包含3個段,下面顯示了調用之後

Figure 5.6 After clear() is called

Netty Buffer(緩沖)Buffer APIByteBuf - 位元組資料的容器位元組級别的操作ByteBufHolderByteBuf 配置設定ByteBuf 配置設定引用計數器

現在 整個 ByteBuf 空間都是可寫的了。

clear() 比 discardReadBytes() 更低成本,因為他隻是重置了索引,而沒有記憶體拷貝。

查詢操作

有幾種方法,以确定在所述緩沖器中的指定值的索引。最簡單的是使用 indexOf() 方法。更複雜的搜尋執行以 ByteBufProcessor 為參數的方法。這個接口定義了一個方法,boolean process(byte value),它用來報告輸入值是否是一個正在尋求的值。

ByteBufProcessor 定義了很多友善實作共同目标值。例如,假設您的應用程式需要內建所謂的“Flash sockets”,将使用 NULL 結尾的内容。調用

forEachByte(ByteBufProcessor.FIND_NUL)
           

通過減少的,因為少量的 “邊界檢查”的處理過程中執行了,進而使 消耗 Flash 資料變得 編碼工作量更少、效率更高。

下面例子展示了尋找一個回車符,

\ r

的一個例子。

Listing 5.9 Using ByteBufProcessor to find 

\r

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

衍生的緩沖區

“衍生的緩沖區”是代表一個專門的展示 ByteBuf 内容的“視圖”。這種視圖是由 duplicate(), slice(), slice(int, int),readOnly(), 和 order(ByteOrder) 方法建立的。所有這些都傳回一個新的 ByteBuf 執行個體包括它自己的 reader, writer 和标記索引。然而,内部資料存儲共享就像在一個 NIO 的 ByteBuffer。這使得衍生的緩沖區建立、修改其 内容,以及修改其“源”執行個體更廉價。

ByteBuf 拷貝

如果需要已有的緩沖區的全新副本,使用 copy() 或者 copy(int, int)。不同于派生緩沖區,這個調用傳回的 ByteBuf 有資料的獨立副本。

若需要操作某段資料,使用 slice(int, int),下面展示了用法:

Listing 5.10 Slice a ByteBuf

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); //1

ByteBuf sliced = buf.slice(0, 14);          //2
System.out.println(sliced.toString(utf8));  //3

buf.setByte(0, (byte) 'J');                 //4
assert buf.getByte(0) == sliced.getByte(0);
           

1.建立一個 ByteBuf 儲存特定位元組串。

2.建立從索引 0 開始,并在 14 結束的 ByteBuf 的新 slice。

3.列印 Netty in Action

4.更新索引 0 的位元組。

5.斷言成功,因為資料是共享的,并以一個地方所做的修改将在其他地方可見。

下面看下如何将一個 ByteBuf 段的副本不同于 slice。

Listing 5.11 Copying a ByteBuf

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);     //1

ByteBuf copy = buf.copy(0, 14);               //2
System.out.println(copy.toString(utf8));      //3

buf.setByte(0, (byte) 'J');                   //4
assert buf.getByte(0) != copy.getByte(0);
           

1.建立一個 ByteBuf 儲存特定位元組串。

2.建立從索引0開始和 14 結束 的 ByteBuf 的段的拷貝。

3.列印 Netty in Action

4.更新索引 0 的位元組。

5.斷言成功,因為資料不是共享的,并以一個地方所做的修改将不影響其他。

代碼幾乎是相同的,但所 衍生的 ByteBuf 效果是不同的。是以,使用一個 slice 可以盡可能避免複制記憶體。

讀/寫操作

讀/寫操作主要由2類:

  • gget()/set() 操作從給定的索引開始,保持不變
  • read()/write() 操作從給定的索引開始,與位元組通路的數量來适用,遞增目前的寫索引或讀索引

ByteBuf 的各種讀寫方法或其他一些檢查方法可以看 ByteBuf 的 API,下面是常見的 get() 操作:

Table 5.1 get() operations

方法名稱 描述
getBoolean(int) 傳回目前索引的 Boolean 值
getByte(int) getUnsignedByte(int) 傳回目前索引的(無符号)位元組
getMedium(int) getUnsignedMedium(int) 傳回目前索引的 (無符号) 24-bit 中間值
getInt(int) getUnsignedInt(int) 傳回目前索引的(無符号) 整型
getLong(int) getUnsignedLong(int) 傳回目前索引的 (無符号) Long 型
getShort(int) getUnsignedShort(int) 傳回目前索引的 (無符号) Short 型
getBytes(int, ...) 位元組

常見 set() 操作如下

Table 5.2 set() operations

方法名稱 描述
setBoolean(int, boolean) 在指定的索引位置設定 Boolean 值
setByte(int, int) 在指定的索引位置設定 byte 值
setMedium(int, int) 在指定的索引位置設定 24-bit 中間 值
setInt(int, int) 在指定的索引位置設定 int 值
setLong(int, long) 在指定的索引位置設定 long 值
setShort(int, int) 在指定的索引位置設定 short 值

下面是用法:

Listing 5.12 get() and set() usage

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);    //1
System.out.println((char)buf.getByte(0));                    //2

int readerIndex = buf.readerIndex();                        //3
int writerIndex = buf.writerIndex();

buf.setByte(0, (byte)'B');                            //4

System.out.println((char)buf.getByte(0));                    //5
assert readerIndex == buf.readerIndex();                    //6
assert writerIndex ==  buf.writerIndex();
           

1.建立一個新的 ByteBuf 給指定 String 儲存位元組

2.列印的第一個字元,

N

3.存儲目前 readerIndex 和 writerIndex

4.更新索引 0 的字元

B

5.列印出的第一個字元,現在

B

6.這些斷言成功,因為這些操作永遠不會改變索引

現在,讓我們來看看 read() 操作,對目前 readerIndex 或 writerIndex 進行操作。這些用于從 ByteBuf 讀取就好像它是一個流。 (對應的 write() 操作用于“追加”到 ByteBuf )。下面展示了常見的  read() 方法。

Table 5.3 read() operations

方法名稱 描述
readBoolean() Reads the Boolean value at the current readerIndex and increases the readerIndex by 1.
readByte() readUnsignedByte() Reads the (unsigned) byte value at the current readerIndex and increases the readerIndex by 1.
readMedium() readUnsignedMedium() Reads the (unsigned) 24-bit medium value at the current readerIndex and increases the readerIndex by 3.
readInt() readUnsignedInt() Reads the (unsigned) int value at the current readerIndex and increases the readerIndex by 4.
readLong() readUnsignedLong() Reads the (unsigned) int value at the current readerIndex and increases the readerIndex by 8.
readShort() readUnsignedShort() Reads the (unsigned) int value at the current readerIndex and increases the readerIndex by 2.
readBytes(int,int, ...) Reads the value on the current readerIndex for the given length into the given object. Also increases the readerIndex by the length.

每個 read() 方法都對應一個 write()。

Table 5.4 Write operations

方法名稱 描述
writeBoolean(boolean) Writes the Boolean value on the current writerIndex and increases the writerIndex by 1.
writeByte(int) Writes the byte value on the current writerIndex and increases the writerIndex by 1.
writeMedium(int) Writes the medium value on the current writerIndex and increases the writerIndex by 3.
writeInt(int) Writes the int value on the current writerIndex and increases the writerIndex by 4.
writeLong(long) Writes the long value on the current writerIndex and increases the writerIndex by 8.
writeShort(int) Writes the short value on the current writerIndex and increases thewriterIndex by 2.
writeBytes(int,...) Transfers the bytes on the current writerIndex from given resources.

Listing 5.13 read()/write() operations on the ByteBuf

Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);    //1
System.out.println((char)buf.readByte());                    //2

int readerIndex = buf.readerIndex();                        //3
int writerIndex = buf.writerIndex();                        //4

buf.writeByte((byte)'?');                            //5

assert readerIndex == buf.readerIndex();
assert writerIndex != buf.writerIndex();
           

1.建立一個新的 ByteBuf 儲存給定 String 的位元組。

2.列印的第一個字元,

N

3.存儲目前的 readerIndex

4.儲存目前的 writerIndex

5.更新索引0的字元 

B

6.此斷言成功,因為 writeByte() 在 5 移動了 writerIndex

更多操作

Table 5.5 Other useful operations

方法名稱 描述
isReadable() Returns true if at least one byte can be read.
isWritable() Returns true if at least one byte can be written.
readableBytes() Returns the number of bytes that can be read.
writablesBytes() Returns the number of bytes that can be written.
capacity() Returns the number of bytes that the ByteBuf can hold. After this it will try to expand again until maxCapacity() is reached.
maxCapacity() Returns the maximum number of bytes the ByteBuf can hold.
hasArray() Returns true if the ByteBuf is backed by a byte array.
array() Returns the byte array if the ByteBuf is backed by a byte array, otherwise throws an

UnsupportedOperationException.

ByteBufHolder

我們經常遇到需要另外存儲除有效的實際資料各種屬性值。 HTTP 響應是一個很好的例子;與内容一起的位元組的還有狀态碼, cookies,等。

Netty 提供 ByteBufHolder 處理這種常見的情況。 ByteBufHolder 還提供對于 Netty 的進階功能,如緩沖池,其中儲存實際資料的 ByteBuf 可以從池中借用,如果需要還可以自動釋放。

ByteBufHolder 有那麼幾個方法。到底層的這些支援接入資料和引用計數。表5.7所示的方法(忽略了那些從繼承 ReferenceCounted 的方法)。

Table 5.7 ByteBufHolder operations

名稱 描述
data() 傳回 ByteBuf 儲存的資料
copy() 制作一個 ByteBufHolder 的拷貝,但不共享其資料(是以資料也是拷貝).

如果你想實作一個“消息對象”有效負載存儲在 ByteBuf,使用ByteBufHolder 是一個好主意。

ByteBuf 配置設定

本節介紹 ByteBuf 執行個體管理的幾種方式:

ByteBufAllocator

為了減少配置設定和釋放記憶體的開銷,Netty 通過支援池類 ByteBufAllocator,可用于配置設定的任何 ByteBuf 我們已經描述過的類型的執行個體。是否使用池是由應用程式決定的,表5.8列出了 ByteBufAllocator 提供的操作。

Table 5.8 ByteBufAllocator methods

名稱 描述
buffer() buffer(int) buffer(int, int) Return a ByteBuf with heap-based or direct data storage.
heapBuffer() heapBuffer(int) heapBuffer(int, int) Return a ByteBuf with heap-based storage.
directBuffer() directBuffer(int) directBuffer(int, int) Return a ByteBuf with direct storage.
compositeBuffer() compositeBuffer(int) heapCompositeBuffer() heapCompositeBuffer(int) directCompositeBuffer()directCompositeBuffer(int) Return a CompositeByteBuf that can be expanded by adding heapbased or direct buffers.
ioBuffer() Return a ByteBuf that will be used for I/O operations on a socket.

通過一些方法接受整型參數允許使用者指定 ByteBuf 的初始和最大容量值。你可能還記得,ByteBuf 存儲可以擴大到其最大容量。

得到一個 ByteBufAllocator 的引用很簡單。你可以得到從 Channel (在理論上,每 Channel 可具有不同的 ByteBufAllocator ),或通過綁定到的 ChannelHandler 的 ChannelHandlerContext 得到它,用它實作了你資料處理邏輯。

下面的清單說明獲得 ByteBufAllocator 的兩種方式。

Listing 5.15 Obtain ByteBufAllocator reference

Channel channel = ...;
ByteBufAllocator allocator = channel.alloc(); //1
....
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc(); //2
...
           

1.從 channel 獲得 ByteBufAllocator

2.從 ChannelHandlerContext 獲得 ByteBufAllocator

Netty 提供了兩種 ByteBufAllocator 的實作,一種是 PooledByteBufAllocator,用ByteBuf 執行個體池改進性能以及記憶體使用降到最低,此實作使用一個“jemalloc”記憶體配置設定。其他的實作不池化 ByteBuf 情況下,每次傳回一個新的執行個體。

Netty 預設使用 PooledByteBufAllocator,我們可以通過 ChannelConfig 或通過引導設定一個不同的實作來改變。更多細節在後面講述 ,見 Chapter 9, "Bootstrapping Netty Applications"

Unpooled (非池化)緩存

當未引用 ByteBufAllocator 時,上面的方法無法通路到 ByteBuf。對于這個用例 Netty 提供一個實用工具類稱為 Unpooled,,它提供了靜态輔助方法來建立非池化的 ByteBuf 執行個體。表5.9列出了最重要的方法

Table 5.9 Unpooled helper class

名稱 描述
buffer() buffer(int) buffer(int, int) Returns an unpooled ByteBuf with heap-based storage
directBuffer() directBuffer(int) directBuffer(int, int) Returns an unpooled ByteBuf with direct storage
wrappedBuffer() Returns a ByteBuf, which wraps the given data.
copiedBuffer() Returns a ByteBuf, which copies the given data

在 非聯網項目,該 Unpooled 類也使得它更容易使用的 ByteBuf API,獲得一個高性能的可擴充緩沖 API,而不需要 Netty 的其他部分的。

ByteBufUtil

ByteBufUtil 靜态輔助方法來操作 ByteBuf,因為這個 API 是通用的,與使用池無關,這些方法已經在外面的配置設定類實作。

也許最有價值的是 hexDump() 方法,這個方法傳回指定 ByteBuf 中可讀位元組的十六進制字元串,可以用于調試程式時列印 ByteBuf 的内容。一個典型的用途是記錄一個 ByteBuf 的内容進行調試。十六進制字元串相比位元組而言對使用者更友好。 而且十六進制版本可以很容易地轉換回實際位元組表示。

另一個有用方法是 使用 boolean equals(ByteBuf, ByteBuf),用來比較 ByteBuf 執行個體是否相等。在 實作自己 ByteBuf 的子類時經常用到。

ByteBuf 配置設定

本節介紹 ByteBuf 執行個體管理的幾種方式:

ByteBufAllocator

為了減少配置設定和釋放記憶體的開銷,Netty 通過支援池類 ByteBufAllocator,可用于配置設定的任何 ByteBuf 我們已經描述過的類型的執行個體。是否使用池是由應用程式決定的,表5.8列出了 ByteBufAllocator 提供的操作。

Table 5.8 ByteBufAllocator methods

名稱 描述
buffer() buffer(int) buffer(int, int) Return a ByteBuf with heap-based or direct data storage.
heapBuffer() heapBuffer(int) heapBuffer(int, int) Return a ByteBuf with heap-based storage.
directBuffer() directBuffer(int) directBuffer(int, int) Return a ByteBuf with direct storage.
compositeBuffer() compositeBuffer(int) heapCompositeBuffer() heapCompositeBuffer(int) directCompositeBuffer()directCompositeBuffer(int) Return a CompositeByteBuf that can be expanded by adding heapbased or direct buffers.
ioBuffer() Return a ByteBuf that will be used for I/O operations on a socket.

通過一些方法接受整型參數允許使用者指定 ByteBuf 的初始和最大容量值。你可能還記得,ByteBuf 存儲可以擴大到其最大容量。

得到一個 ByteBufAllocator 的引用很簡單。你可以得到從 Channel (在理論上,每 Channel 可具有不同的 ByteBufAllocator ),或通過綁定到的 ChannelHandler 的 ChannelHandlerContext 得到它,用它實作了你資料處理邏輯。

下面的清單說明獲得 ByteBufAllocator 的兩種方式。

Listing 5.15 Obtain ByteBufAllocator reference

Channel channel = ...;
ByteBufAllocator allocator = channel.alloc(); //1
....
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc(); //2
...
           

1.從 channel 獲得 ByteBufAllocator

2.從 ChannelHandlerContext 獲得 ByteBufAllocator

Netty 提供了兩種 ByteBufAllocator 的實作,一種是 PooledByteBufAllocator,用ByteBuf 執行個體池改進性能以及記憶體使用降到最低,此實作使用一個“jemalloc”記憶體配置設定。其他的實作不池化 ByteBuf 情況下,每次傳回一個新的執行個體。

Netty 預設使用 PooledByteBufAllocator,我們可以通過 ChannelConfig 或通過引導設定一個不同的實作來改變。更多細節在後面講述 ,見 Chapter 9, "Bootstrapping Netty Applications"

Unpooled (非池化)緩存

當未引用 ByteBufAllocator 時,上面的方法無法通路到 ByteBuf。對于這個用例 Netty 提供一個實用工具類稱為 Unpooled,,它提供了靜态輔助方法來建立非池化的 ByteBuf 執行個體。表5.9列出了最重要的方法

Table 5.9 Unpooled helper class

名稱 描述
buffer() buffer(int) buffer(int, int) Returns an unpooled ByteBuf with heap-based storage
directBuffer() directBuffer(int) directBuffer(int, int) Returns an unpooled ByteBuf with direct storage
wrappedBuffer() Returns a ByteBuf, which wraps the given data.
copiedBuffer() Returns a ByteBuf, which copies the given data

在 非聯網項目,該 Unpooled 類也使得它更容易使用的 ByteBuf API,獲得一個高性能的可擴充緩沖 API,而不需要 Netty 的其他部分的。

ByteBufUtil

ByteBufUtil 靜态輔助方法來操作 ByteBuf,因為這個 API 是通用的,與使用池無關,這些方法已經在外面的配置設定類實作。

也許最有價值的是 hexDump() 方法,這個方法傳回指定 ByteBuf 中可讀位元組的十六進制字元串,可以用于調試程式時列印 ByteBuf 的内容。一個典型的用途是記錄一個 ByteBuf 的内容進行調試。十六進制字元串相比位元組而言對使用者更友好。 而且十六進制版本可以很容易地轉換回實際位元組表示。

另一個有用方法是 使用 boolean equals(ByteBuf, ByteBuf),用來比較 ByteBuf 執行個體是否相等。在 實作自己 ByteBuf 的子類時經常用到。

引用計數器

Netty 4 引入了 引用計數器給 ByteBuf 和 ByteBufHolder(兩者都實作了 ReferenceCounted 接口)

引用計數本身并不複雜;它在特定的對象上跟蹤引用的數目。實作了ReferenceCounted 的類的執行個體會通常開始于一個活動的引用計數器為 1。活動的引用計數器大于0的對象被保證不被釋放。當數量引用減少到0,該執行個體将被釋放。需要注意的是“釋放”的語義是特定于具體的實作。最起碼,一個對象,它已被釋放應不再可用。

這種技術就是諸如 PooledByteBufAllocator 這種減少記憶體配置設定開銷的池化的精髓部分。

Listing 5.16 Reference counting

Channel channel = ...;
ByteBufAllocator allocator = channel.alloc(); //1
....
ByteBuf buffer = allocator.directBuffer(); //2
assert buffer.refCnt() == 1; //3
...
           

1.從 channel 擷取 ByteBufAllocator

2.從 ByteBufAllocator 配置設定一個 ByteBuf

3.檢查引用計數器是否是 1

Listing 5.17 Release reference counted object

ByteBuf buffer = ...;
boolean released = buffer.release(); //1
...
           

1.release()将會遞減對象引用的數目。當這個引用計數達到0時,對象已被釋放,并且該方法傳回 true。

如果嘗試通路已經釋放的對象,将會抛出 IllegalReferenceCountException 異常。

需要注意的是一個特定的類可以定義自己獨特的方式其釋放計數的“規則”。 例如,release() 可以将引用計數器直接計為 0 而不管目前引用的對象數目。

誰負責 release?

在一般情況下,最後通路的對象負責釋放它。在第6章我們會解釋 ChannelHandler 和 ChannelPipeline 的相關概念。