網路資料的基本單位總是位元組。Java NIO 提供了 ByteBuffer 作為它的位元組容器,但是這個類使用起來過於複雜,而且也有些繁瑣。Netty 的 ByteBuffer 替代品是 ByteBuf,一個強大的實現,既解決了 JDK API 的侷限性,又為網路應用程式的開發者提供了更好的 API
ByteBuf優勢
- 它可以被使用者自定義的緩衝區型別擴充套件
- 通過內建的複合緩衝區型別實現了透明的零拷貝
- 容量可以按需增長
- 在讀和寫這兩種模式之間切換不需要呼叫 ByteBuffer 的 flip()方法
- 讀和寫使用了不同的索引
- 支援方法的鏈式呼叫
- 支援引用計數
- 支援池化
ByteBuf實現原理
如圖ByteBuf通維護了兩個不同的索引:一個用於讀取,一個用於寫入。
當你從 ByteBuf 讀取時,它的 readerIndex 將會被遞增已經被讀取的位元組數。同樣地,當你寫入 ByteBuf 時,它的writerIndex 也會被遞增
當呼叫readBytes時,readIndex會相應移動length位,如果readIndex移動後大於writeIndex則會拋異常。
當呼叫writeBytes時,writeIndex會相應移動length位,且通過ensureWritable方法實現自動擴容
其他常用API
getBytes | 獲取可讀位元組陣列 |
setBytes | 寫入位元組 |
discardReadBytes | 廢棄已讀位元組 |
mark | 標記index |
reset | 將index重置到之前標記的位置(配合mark使用) |
isReadable | 如果至少有一個位元組可供讀取,則返回 true |
isWritable | 如果至少有一個位元組可被寫入,則返回 true |
readableBytes | 返回可被讀取的位元組數 |
writableBytes | 返回可被寫入的位元組數 |
capacity | 返回 ByteBuf 可容納的位元組數。在此之後,它會嘗試再次擴充套件直到達到 maxCapacity() |
maxCapacity | 返回 ByteBuf 可以容納的最大位元組數 |
hasArray | 如果 ByteBuf 由一個位元組陣列支撐,則返回 true |
array | 如果 ByteBuf 由一個位元組陣列支撐則返回該陣列;否則,它將丟擲一個UnsupportedOperationException 異常 |
ByteBuf緩衝分類
1、Heap buffer(堆緩衝區):
就是將資料存在JVM堆空間中,在沒有被池化的情況可以快速分配和釋放。
優點:由於資料是儲存在JVM堆中,因此可以快速的建立與快速的釋放,並且它提供了直接訪問內部位元組陣列的方法。
缺點:每次讀寫資料時,都需要先將資料複製到直接緩衝區中再進行網路傳輸。
2、Direct buffer(直接緩衝區):
直接緩衝區,在堆外直接分配記憶體空間,直接緩衝區並不會佔用堆的容量空間,因為它是由作業系統在本地記憶體進行的資料分配。
優點:在使用Socket進行資料傳遞時,效能非常好,因為資料直接位於作業系統的本地記憶體中,所以不需要從JVM將資料複製到直接緩衝區中 。
缺點:因為Direct Buffer是直接在作業系統記憶體中的,所以記憶體空間的分配與釋放要比堆空間更加複雜,而且速度要慢一些。
注意:
如果你的資料包含在一個在堆上的分配的緩衝區中,那麼事實上,在通過套接字傳送他之前,jvm將會在內部把你的緩衝區複製到一個直接緩衝區中;這樣分配釋放就比較浪費資源;
建議:
直接緩衝區並不支援通過位元組陣列的方式來訪問資料。對於後端業務的訊息編解碼來說,推薦使用HeapByteBuf;對於I/O通訊執行緒在讀寫緩衝區時,推薦使用DirectByteBuf;
3、Composite Buffer 複合緩衝區:
可以擁有以上兩種的緩衝區,通過一種聚合檢視來操作底層持有的多種型別Buffer。這種緩衝,jdk nio是沒有這種特性的。
ByteBuf主要實現類
pooled:池化,重用ByteBuf物件
Direct:直接記憶體,內部通過ByteBuffer實現,典型裝飾模式
Heap:堆記憶體,內部持有byte陣列
(1)UnpooledDirectByteBuf:
在堆外進行記憶體分配的非記憶體池ByteBuf,內部持有ByteBuffer物件,相關操作委託給ByteBuffer實現。
(2)UnpooledHeapByteBuf:
基於堆記憶體分配非記憶體池ByteBuf,即內部持有byte陣列。
(3)UnpooledUnsafeDirectByteBuf:
和另外一個類UnpooledDirectByteBuf差不多相同,區別在於UnpooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,依賴平臺。
(4)ReadOnlyByteBufferBuf:
只讀ByteBuf,內部持有ByteBuffer物件,相關操作委託給ByteBuffer實現,該ByteBuf限內部使用;
(5)FixedCompositeByteBuf:
用於將多個ByteBuf組合在一起,形成一個虛擬的只讀ByteBuf物件,不允許寫入和動態擴充套件。內部使用Object[]將多個ByteBuf組合在一起,一旦FixedCompositeByteBuf物件構建完成,則不會被更改。
(6)CompositeByteBuf:
用於將多個ByteBuf組合在一起,形成一個虛擬的ByteBuf物件,支援讀寫和動態擴充套件。內部使用List組合多個ByteBuf。一般使用使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工廠方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)建立CompositeByteBuf物件。
(7)PooledByteBuf:
基於記憶體池的ByteBuf,主要為了重用ByteBuf物件,提升記憶體的使用效率;適用於高負載,高併發的應用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三個子類,PooledDirectByteBuf是在堆外進行記憶體分配的記憶體池ByteBuf,PooledHeapByteBuf是基於堆記憶體分配記憶體池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外進行記憶體分配的記憶體池ByteBuf,區別在於PooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,具有平臺相關性。
ByteBufHolder
利用組合的方式對ByteBuf進行擴充套件。實際應用中我們經常發現,除了實際的資料負載之外,我們還需要儲存各種屬性值。HTTP 響應便是一個很好的例子,除了表示為位元組的內容,還包括狀態碼、cookie 等。為了處理這種常見的用例,Netty 提供了 ByteBufHolder。ByteBufHolder 也為 Netty 的高階特性提供了支援,如緩衝區池化,其中可以從池中借用 ByteBuf,並且在需要時自動釋放。
ByteBufAllocator
為了降低分配和釋放記憶體的開銷,Netty 通過 ByteBufAllocator 實現了(ByteBuf 的)池化,它可以用來分配我們所描述過的任意型別的 ByteBuf 例項。Netty提供了兩種ByteBufAllocator的實現:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的例項以提高效能並最大限度地減少記憶體碎片。
ReferenceCounted
類似GC引用計數法,Netty中ByteBuf和ByteBufHolder都實現了該介面,其主要通過方法retain(release)來增加(減少)資源被引用的次數,當引用為0時,代表該資源可以被釋放。