一、前言
前面已經學習了Netty中傳輸部分,現在接着學習Netty中的ByteBuf。
二、ByteBuf
2.1 ByteBuf API
在網絡上傳輸的數據形式為Byte,Java NIO提供了ByteBuffer來作為Byte容器,該類有些復雜,而Netty使用ByteBuf作為ByteBuffer的替換方案,其提供了一個更好的API,
Netty通過ByteBuf和ByteBufHolder兩個組件處理數據,而ByteBuf的API有如下優勢
· 可擴展的用戶定義的緩沖區類型
· 通過內置復合緩沖區類型實現透明零拷貝
· 容量隨着需求可擴大
· 在讀寫器模式之間切換不需要調用ByteBuffer的flip()方法
· 數據讀寫使用不同的索引
· 支持方法鏈
· 支持引用計數
· 支持池
ByteBuf維護兩個不同的讀索引和寫索引,當讀ByteBuf時,readerIndex會隨着數據的讀取而不斷增加,同理,writerIndex也相同,對於空的ByteBuf而言,其readerIndex和writerIndex均初始化為0,如下圖所示
而當讀取數據時,readerIndex與writerIndex相同時,表示不能再讀取數據了,否則會拋出IndexOutOfBoundsException異常,以read或者write開頭的方法會增加相應的索引,而set和get方法則不會,可自定義ByteBuf的大小,當超過大小時將拋出異常。
2.2 ByteBuf使用模式
常用的模式有如下幾種:
· 堆緩沖。將數據存儲在JVM的堆空間中,使用backing array提供支持,這種模式在不使用池的情況下提供快速分配和釋放。
· 直接緩沖。通過JVM的本地調用分配內存,這可避免每次調用本地I / O操作之前(或之后)將緩沖區的內容復制到(或從)中間緩沖區。
· 復合緩沖。呈現多個ByteBufs的聚合視圖,可以添加或刪除ByteBuf實例,由Netty中的CompositeByteBuf提供支持,CompositeByteBuf中的ByteBuf實例包含直接或非直接的分配。
2.3 字節級的操作
ByteBuf提供了很多用於修改數據的讀寫方法。
1. 隨機訪問索引
ByteBuf的第一個索引編號為0,最后一個編號為capacity() - 1,可使用如下代碼讀取ByteBuf的數據
for (int i = 0; i < buffer.capacity(); i++) { byte b = buffer.getByte(i); System.out.println((char) b); }
值得注意的是當有索引作為參數傳入方法而讀取數據時,並不會改變readerIndex或者writerIndex的值。
2. 順序訪問索引
Netty的ByteBuf有讀寫兩個索引,而JDK的ByteBuffer只有一個索引,因此需要使用flip方法進行讀寫切換,下圖展示了讀寫索引如何將ByteBuf划分為三個區域。
3. 可舍棄字節
可舍棄的字節表示那些已經被讀取的數據,可通過調用discardReadBytes() 方法舍棄並且回收該部分空間。當調用了discardReadBytes方法后,其布局如下圖所示
可以看到,整個容量未變,但是此時readerIndex的值變為0,可寫的容量大小擴大了。
4. 可讀字節
可讀字節部分存儲了真實的數據。新分配的、包裝的或復制的緩沖區的readerIndex的默認值為0,以read或者skip開頭的方法操作將檢索或跳過當前readerIndex中的數據並增加讀取的字節數,當可讀字節已經用盡時,再進行讀取將會拋出異常。下面代碼將會讀取ByteBuf中的所有數據。
ByteBuf buffer = ...; while (buffer.isReadable()) { System.out.println(buffer.readByte()); }
5. 可寫字節
可寫字節部分可供寫入數據,初始化的writerIndex為0,以write開頭的將會從writerIndex開始寫入數據,並且writerIndex會增加相應的大小。當超過ByteBuf的容量時,再寫入數據時會拋出IndexOutOfBoundException異常,如下代碼會隨機寫入一個整形。
ByteBuf buffer = ...; while (buffer.writableBytes() >= 4) { buffer.writeInt(random.nextInt()); }
6. 索引管理
可通過調用markReaderIndex(), markWriterIndex(), resetReaderIndex(), and resetWriterIndex()方法來標記和重置readerIndex和writerIndex,也可通過調用readerIndex(int)、writerIndex(int) 方法來將readerIndex和writerIndex設置為指定值,也可通過調用clear()方法將readerIndex和writerIndex設置為0,但是並不會清空內容。
若調用clear之前的布局如下
則調用clear之后的布局如下
clear()方法比discardReadBytes()方法性能更優,因為其不需要拷貝數據。
7. 搜索操作
有多種方法確定ByteBuf指定值的索引,如使用indexOf方法,另一種更為復雜的方法是使用ByteBufProcessor作為方法的參數,如下代碼尋找\r的索引
ByteBuf buffer = ...; int index = buffer.forEachByte(ByteBufProcessor.FIND_CR);
8. 派生緩沖區
派生緩沖提供了ByteBuf的視圖,可通過如下方法創建視圖duplicate()、slice()、slice(int, int)、Unpooled.unmodifiableBuffer(…)、order(ByteOrder)、readSlice(int)。
每個方法將會返回一個新的ByteBuf實例,該實例有自己的readerIndex、writerIndex、marker索引,當修改該ByteBuf實例數據時,原始數據也將被修改。如下代碼展示了使用slice方法來創建新的ByteBuf實例用法
Charset utf8 = Charset.forName("UTF-8"); ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); ByteBuf sliced = buf.slice(0, 14); System.out.println(sliced.toString(utf8)); buf.setByte(0, (byte)'J'); assert buf.getByte(0) == sliced.getByte(0);
其中,由於數據是共享的,對一個ByteBuf的修改對原始的ByteBuf是可見的
下面代碼展示了copy方法的使用
Charset utf8 = Charset.forName("UTF-8"); ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); ByteBuf copy = buf.copy(0, 14); System.out.println(copy.toString(utf8)); buf.setByte(0, (byte)'J'); assert buf.getByte(0) != copy.getByte(0);
copy方法會重新分配新的ByteBuf,對其的修改對原始的ByteBuf不可見。
9. 讀/寫操作
get和set操作讀寫指定索引的數據,而不會改變索引值。read和write操作讀寫指定索引數據,並且會改變索引的值。
2.4 ByteBuf分配
1. ByteBufAllocator接口
為減少分配和重新分配內存的開銷,Netty使用ByteBufAllocator使用了池,其可分配任何類型的ByteBuf實例,Netty提供了ByteBufAllocator兩種類型的實現:PooledByteBufAllocator和UnpooledByteBufAllocator,前者池化ByteBuf實例以提高性能並最小化內存碎片,后者每次調用時都返回一個新的實例。Netty默認使用PooledByteBufAllocator,但也可通過ChannelConfig改變並使用不同的分配器。
2. 非池化緩沖
當沒有ByteBufAllocator引用時,Netty提供了Unpooled工具類,其提供了創建非池化緩沖的幫助方法,具體如下:buffer()、buffer(int initialCapacity)、buffer(int initialCapacity, int maxCapacity)、directBuffer()、directBuffer(int initialCapacity)、directBuffer(int initialCapacity, int maxCapacity)、wrappedBuffer()、copiedBuffer()等。
3. ByteBufUtil類
ByteBufUtil類提供了管理ByteBuf的方法,其中最有效的方法是hexdump方法,它打印ByteBuf的內容的十六進制表示,在調試時該方法非常有用。另一個方法是boolean equals(ByteBuf, ByteBuf) 方法,用來判斷兩個ByteBuf的相等性。
2.5 引用計數
引用計數是一種通過釋放由對象不再被其他對象引用的對象所持有的資源來優化內存使用和性能的技術,Netty在ByteBuf和ByteBufHolder的第4版中引入了引用計數,其都實現了ReferenceCounted接口。引用計數背后的思想並不復雜,主要是跟蹤指定對象的活動引用數。ReferenceCounted實現實例的初始化活動引用計數為1。只要引用計數大於0,就要保證對象不被釋放,當為0時,需要被釋放。當訪問已經被釋放的對象時會拋出IllegalReferenceCountException異常。
三、總結
本篇博文着重講解了ByteBuf的具體細節,以及講解了不同的緩沖區類型,其是Netty中的核心概念,可以類比JDK中的ByteBuffer進行學習,也謝謝各位園友的觀看~