Netty 在數據傳輸過程當中,會使用緩衝區設計來提升傳輸效率。雖然,Java 在 NIO 編程中已提供 ByteBuffer 類進行使用,可是在使用過程當中,其編碼方式相對來講不太友好,也存在必定的不足。因此高性能的 Netty 框架實現了一套更增強大,完善的 ByteBuf,其設計理念也是堪稱一絕。java
在分析 ByteBuf 以前,先簡單講下 ByteBuffer 類的操做。便於更好理解 ByteBuf 。編程
ByteBuffer 的讀寫操做共用一個位置指針,讀寫過程經過如下代碼案例分析:數組
// 分配一個緩衝區,並指定大小 ByteBuffer buffer = ByteBuffer.allocate(100); // 設置當前最大緩存區大小限制 buffer.limit(15); System.out.println(String.format("allocate: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); String content = "ytao公衆號"; // 向緩衝區寫入數據 buffer.put(content.getBytes()); System.out.println(String.format("put: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));
其中打印了緩衝區三個參數,分別是:緩存
打印結果:框架
當咱們寫入內容後,讀寫指針值爲 13,ytao公衆號
英文字符佔 1 個 byte,每一箇中文佔 4 個 byte,恰好 13,小於設置的當前緩衝區大小 15。函數
接下來,讀取內容裏的 ytao 數據:性能
buffer.flip(); System.out.println(String.format("flip: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); byte[] readBytes = new byte[4]; buffer.get(readBytes); System.out.println(String.format("get(4): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); String readContent = new String(readBytes); System.out.println("readContent:"+readContent);
讀取內容須要建立個 byte 數組來接收,並制定接收的數據大小。編碼
在寫入數據後再讀取內容,必須主動調用ByteBuffer#flip
或ByteBuffer#clear
。設計
ByteBuffer#flip
它會將寫入數據後的指針位置值做爲當前緩衝區大小,再將指針位置歸零。會使寫入數據的緩衝區改成待取數據的緩衝區,也就是說,讀取數據會從剛寫入的數據第一個索引做爲讀取數據的起始索引。指針
ByteBuffer#flip
相關源碼:
ByteBuffer#clear
則會重置 limit 爲默認值,與 capacity 大小相同。
接下讀取剩餘部份內容:
第二次讀取的時候,可以使用buffer#remaining
來獲取大於或等於剩下的內容的字節大小,該函數實現爲limit - position
,因此當前緩衝區域必定在這個值範圍內。
readBytes = new byte[buffer.remaining()]; buffer.get(readBytes); System.out.println(String.format("get(remaining): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));
打印結果:
以上操做過程當中,索引變化如圖:
ByteBuf 有讀寫指針是分開的,分別是buf#readerIndex
和buf#writerIndex
,當前緩衝器大小buf#capacity
。
這裏緩衝區被兩個指針索引和容量劃分爲三個區域:
以下圖所示:
ByteBuf 分配一個緩衝區,僅僅給定一個初始值就能夠。默認是 256。初始值不像 ByteBuffer 同樣是最大值,ByteBuf 的最大值是Integer.MAX_VALUE
ByteBuf buf = Unpooled.buffer(13); System.out.println(String.format("init: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印結果:
ByteBuf 寫操做和 ByteBuffer 相似,只是寫指針是單獨記錄的,ByteBuf 的寫操做支持多種類型,有如下多個API:
寫入字節數組類型:
String content = "ytao公衆號"; buf.writeBytes(content.getBytes()); System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印結果:
索引示意圖:
同樣的,ByteBuf 寫操做和 ByteBuffer 相似,只是寫指針是單獨記錄的,ByteBuf 的讀操做支持多種類型,有如下多個API:
從當前 readerIndex 位置讀取四個字節內容:
byte[] dst = new byte[4]; buf.readBytes(dst); System.out.println(new String(dst)); System.out.println(String.format("read(4): ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印結果:
索引示意圖:
經過上面的 ByteBuffer 分配緩衝區例子,向裏面添加 [ytao公衆號ytao公衆號] 內容,使寫入的內容大於 limit 的值。
ByteBuffer buffer = ByteBuffer.allocate(100); buffer.limit(15); String content = "ytao公衆號ytao公衆號"; buffer.put(content.getBytes());
運行結果異常:
內容字節大小超過了 limit 的值時,緩衝區溢出異常,因此咱們每次寫入數據前,得檢查緩區大小是否有足夠空間,這樣對編碼上來講,不是一個好的體驗。
使用 ByteBuf 添加一樣的內容,給定一樣的初始容器大小。
ByteBuf buf = Unpooled.buffer(15); String content = "ytao公衆號ytao公衆號"; buf.writeBytes(content.getBytes()); System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印運行結果:
經過上面打印信息,能夠看到 cap 從設置的 15 變爲了 64,當咱們容器大小不夠時,就是進行擴容,接下來咱們分析擴容過程當中是如何作的。
進入 writeBytes 裏面:
校驗寫入內容長度:
在可寫區域檢查裏:
writableBytes()
函數爲可寫區域大小capacity - writerIndex
在容量不足,從新分配緩衝區的裏面,以 4M 爲閥門:
Netty 實現的緩衝區,八個基本類型中,除了布爾類型,其餘7種都有本身對應的 Buffer,可是實際使用過程當中, ByteBuf 纔是咱們嘗試用的,它可兼容任何類型。ByteBuf 在 Netty 體系中是最基礎也是最重要的一員,要想更好掌握和使用 Netty,先理解並掌握 ByteBuf 是必需條件之一。
我的博客: https://ytao.top
關注公衆號 【ytao】,更多原創好文