除了基本的讀寫操作, ByteBuf 還提供了它所包含的數據的修改方法。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#隨機訪問索引)隨機訪問索引
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
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.3%20ByteBuf%20internal%20segmentation.jpg)
1.字節,可以被丟棄,因為它們已經被讀
2.還沒有被讀的字節是:“readable bytes(可讀字節)”
3.空間可加入多個字節的是:“writeable bytes(寫字節)”
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#可丟棄字節的字節)可丟棄字節的字節
標有“可丟棄字節”的段包含已經被讀取的字節。他們可以被丟棄,通過調用discardReadBytes() 來回收空間。這個段的初始大小存儲在readerIndex,為 0,當“read”操作被執行時遞增(“get”操作不會移動 readerIndex)。
圖5.4示出了在 圖5.3 中的緩沖區中調用 discardReadBytes() 所示的結果。你可以看到,在丟棄字節段的空間已變得可用寫。需要注意的是不能保證對可寫的段之后的內容在 discardReadBytes() 方法之后已經被調用。
Figure 5.4 ByteBuf after discarding read bytes.
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.4%20ByteBuf%20after%20discarding%20read%20bytes.jpg)
1.字節尚未被讀出(readerIndex 現在 0)。 2.可用的空間,由于空間被回收而增大。
ByteBuf.discardReadBytes() 可以用來清空 ByteBuf 中已讀取的數據,從而使 ByteBuf 有多余的空間容納新的數據,但是discardReadBytes() 可能會涉及內存復制,因為它需要移動 ByteBuf 中可讀的字節到開始位置,這樣的操作會影響性能,一般在需要馬上釋放內存的時候使用收益會比較大。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#可讀字節)可讀字節
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());
}
~~~
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#索引管理)索引管理
在 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
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.5%20Before%20clear%20is%20called.jpg)
調用之前,包含3個段,下面顯示了調用之后
Figure 5.6 After clear() is called
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.6%20After%20clear%20is%20called.jpg)
現在 整個 ByteBuf 空間都是可寫的了。
clear() 比 discardReadBytes() 更低成本,因為他只是重置了索引,而沒有內存拷貝。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#查詢操作)查詢操作
有幾種方法,以確定在所述緩沖器中的指定值的索引。最簡單的是使用 indexOf() 方法。更復雜的搜索執行以 ByteBufProcessor 為參數的方法。這個接口定義了一個方法,boolean process(byte value),它用來報告輸入值是否是一個正在尋求的值。
ByteBufProcessor 定義了很多方便實現共同目標值。例如,假設您的應用程序需要集成所謂的“[Flash sockets](http://help.adobe.com/en_US/as3/dev/WSb2ba3b1aad8a27b0-181c51321220efd9d1c-8000.html)”,將使用 NULL 結尾的內容。調用
~~~
forEachByte(ByteBufProcessor.FIND_NUL)
~~~
通過減少的,因為少量的 “邊界檢查”的處理過程中執行了,從而使 消耗 Flash 數據變得 編碼工作量更少、效率更高。
下面例子展示了尋找一個回車符,`\ r`的一個例子。
Listing 5.9 Using ByteBufProcessor to find?`\r`
~~~
ByteBuf buffer = ...;
int index = buffer.forEachByte(ByteBufProcessor.FIND_CR);
~~~
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#衍生的緩沖區)衍生的緩沖區
“衍生的緩沖區”是代表一個專門的展示 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 可以盡可能避免復制內存。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#讀寫操作)讀/寫操作
讀/寫操作主要由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
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Byte-level%20Operations.md#更多操作)更多操作
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.
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter