io.netty.buffer包中是netty ByteBuf的實現。ByteBuf是一個二進制緩衝區的抽象接口,它的功能有:
- 可以隨機訪問、順序訪問。
- 支持基本數據類型(byte, short, int, long, float, double)的序列化和反序列化。
- 不限制底層原始的數據類型,可以是byte[], NIO Buffer, String, IO Stream, 直接內(C語言中可以表示爲指向一塊內置起始地址的指針void*, 內存的長度), 等等。
爲什麼需要ByteBuf
緩衝區的使用範圍非常廣泛:I/O操作,序列化/反序列化,編碼轉換,壓縮/解壓,加/解密等所有需要使用byte[]的場景。
有些場景需要需要能夠快速地創建和銷燬緩衝區,如:高併發的服務器,處理請求、返回響應的時時候需要大量且高頻地創建,銷燬緩衝區。
有些場景不能確定需要多大的緩衝區,如: 從數據流中分離出一條消息,消息的長度不確定,只知道最大長度。假如消息的最大長度是64KB,而消息的平均長度只有4KB, 如果每次創建64KB的緩衝區就太浪費了。如果緩衝區能夠在需要的時候自動且高效地增減容量,就完美了。
在所有的場景中都涉及到頻繁的數據copy,這要求緩衝區數據copy的性能要儘量高。如果有可能,儘量減少數據copy。
ByteBuf就是爲解決以上問題而設計的。
核心概念
如下所示
| discardable bytes | readable bytes | writable bytes |
0 readerIndex writerIndex capacity
- capacity: 緩衝區的容量。
- readerIndex: 當前讀的位置。可以使用readerIndex()和readerIndex(int)方法獲取、設置readerIndex值。每次調用readXXX方法都會導致readerIndex向writerIndex移動,直到等於writerIndex爲止。
- writerIndex: 寫的當前位置。可以使用writerIndex()和writerIndex(int)方法獲取、設置writeIndex的值。每次調用writeXXX方法都會導致writeIndex向capacity移動,直到等於capacity爲止。
- discardable bytes: 可丟棄的數據。0–readerIndex之間的數據, 長度是readerIndex - 0,調用discardReadBytes會丟棄這部分數據,把readerIndex–writerIndex之間的數據移動到ByteBuf的開始位置(0), ByteBuf會變成如下所示的樣子:
| readable bytes | writable bytes |
readerIndex(0) writerIndex capacity
- readable bytes: 可讀數據。 readerIndex–writerIndex之間的數據,長度是writerInex - readerIndex。可以調用readableBytes()方法得到它的長度。
- writeable bytes: 可寫的空間。長度是capacity - writerIndex。也可以認爲它是ByteBuf的剩餘空間。
核心能力
對二進制數據的讀寫是ByteBuf的核心能力。它提供兩種讀寫方式:
- 隨機讀寫: getXXX(int), setXXX(int, …)。不會對readerIndex和writerIndex產生影響,範圍是(0,capacity]。
- 順序讀寫: readXXX, 增加readerIndex的值,範圍是(readerIndex, writerIndex]。 writeXXX,增加writerIndex值,範圍是(writerIndex, capacity]。
ByteBuf爲了方便使用,提供了一些基本數據類型(unsigned表示無符號類型)的讀寫支持:
數據類型 | 長度(Byte) |
---|---|
byte, unsignedByte | 1 |
short, unsignedShort | 2 |
char | 2 |
medium, unsignedMedium | 3 |
int, unsignedInt | 4 |
long | 8 |
float | 4 |
double | 8 |
對這些基本數據類型的讀寫涉及到了字節須的問題,ByteBuf支持兩種字節序,使用java.nio.ByteOrder中的定義,默認的字節序是BIG_ENDIAN, 這個也是網絡字節序。
此外還提供了對byte[]類型及可以當成byte[]使用的數據類型的支持, 方法名都是:getBytes,setBytes, readBytes, writeBytes。
- byte[]
- ByteBuf
- ByteBuffer
- GatheringByteChannel
- InputStread, OutputStream
內存管理
內存管理分爲兩個部分:內存分配,內存釋放。
ByteBufAllocator是內存分配的接口,它有兩個具體實現:UnpooledByteBufAllocator, PooledByteBufAllocator。
UnpooledByteBufAllocator是JVM內存分配接口的簡單封裝。
PooledByteBufAllocator在JVM內存分配的基礎上實現了一套獨立的內存分配算法。
內存釋放的關鍵是如何判定對象死亡,ByteBuf繼承了ReferenceCounted接口,使用引用計數的方式判定對象死亡。
PooledByteBufAllocator中高效的內存管理算法是ByteBuf的性能基礎,理解了它的算法是理解ByteBuf的關鍵。
體系結構
上圖是ByteBuf體系結構中主要的類和接口。主要分爲三大類:
- AbstractReferenceCountedByteBuf及其子類。這個類別是是ByteBuf中最重要的部分,它分爲4個小類別:
CompositeByteBuf, FixedCompositeByteBuf: 把不同的ByteBuf組合成一個ByteBuf。
PooledByteBuf:實現了自定義內存管理算法的。
UnpooledXXXX: 直接使用JVM內存管理能力。
ReadOnlyByteBufferBuf: 只讀的。
- SwappedByteBuf: 用來轉換ByteBuf的字節序。
- WrappedByteBuf: 用來包裝另一個ByteBuf。
工具
有兩個工具類幫助開發者使用ByteBuf:
- ByteBufUtil: 這個類中創建好了默認的ByteBufAllocator,可以直接拿來用。還有一些操作ByteBuf常用的方法。
- Unpooled: 這個類是針對UnpooledXXXByteBuf的工具。
用法
創建ByteBufAllocator
使用ByteBufUtil.DEFAULT_ALLOCATOR得到ByteBufAllocator實例。這個實例可能是UnpooledByteBufAllocator,也可能是PooledByteBufAllocator類型,這取決於io.netty.allocator.type屬性的設置。默認是unpooled,UnpooledByteBufAllocator類型。如果想要PooledByteBufAllocator類型,把這個屬性的值設置成pooled:
java -Dio.netty.allocator.type=pooled,或者System.setProperty(“io.netty.allocator.type”, “pooled”)
創建ByteBuf
netty不建議直接創建創建ByteBuf實例,推薦使用ByteBufAllocator創建ByteBuf實例,或者使用Unpooled靜態方法。
ByteBufAllocator有7種方法用於創建ByteBuf實例:
方法名 | 特性 |
---|---|
buffer | 使用可能是JVM堆內存或直接內存,取決於具體的實現 |
ioBuffer | 如果可以的話優先使用直接內存 |
heapBuffer | 使用堆內存 |
directBuffer | 使用直接內存 |
CompositeByteBuf | 使用可能是JVM堆內存或直接內存,取決於具體的實現 |
compositeHeapBuffer | 使用堆內存 |
compositeDirectBuffer | 使用堆內存 |
Unpooled創建ByteBuf實例的方法有2兩種:
方法名 | 特性 |
---|---|
buffer | 使用堆內存 |
directBuffer | 使用直接內存 |
包裝成ByteBuf
Unpooled提供了一系列的wrappedBuffer方法,把一些數據類型包裝成一個ByteBuf, 這些數據類型有:
- byte[]。
- ByteBuffer。
- 另一個的ByteBuf中的可讀數據。
wrappedBuffer方法還可以把byte[]…, ByteBuffer…, ByteBuf…包裝成一個CompositeByteBuf。
數據讀寫
數據讀寫是ByteBuf的基本功能,前面已經講過,相關方法是: getXXX, readXXX, setXXX, writeXXX。