1. ByteBuf API的優(yōu)點
- 可以被擴(kuò)展
- 通過內(nèi)置的復(fù)合緩沖區(qū)類型實現(xiàn)了透明的零拷貝
- 容量可以按需增長
- 讀寫模式切換不需要調(diào)用flip方法
- 讀寫使用不用的索引
- 方法支持鏈?zhǔn)秸{(diào)用
- 支持引用計數(shù)
- 支持池化
2.ByteBuf如何工作
ByteBuf維護(hù)了兩個索引,一個讀索引(readerIndex),一個寫索引(writerIndex),讀取數(shù)據(jù)時readerIndex增加,寫入數(shù)據(jù)時writerIndex增加,當(dāng)readerIndex達(dá)到writerIndex相同值時,再繼續(xù)讀取數(shù)據(jù)將會觸發(fā)一個IndexOutOfBoundException
3.ByteBuf使用模式
- 堆緩沖區(qū)
最常用的模式是將數(shù)據(jù)存儲到JVM對空間中,這種模式稱為支撐數(shù)組,它能在沒有使用池化的情況下提供快速的分配和釋放。例子如下
ByteBuf byteBuf = ...;
if (byteBuf.hasArray()) {
byte[] bytes = byteBuf.array();
int offset = byteBuf.arrayOffset() + byteBuf.readerIndex();
int length = byteBuf.readableBytes();
hanle(array, offset, length);
}
當(dāng)hasArray方法返回false時,嘗試獲取支撐數(shù)組將會引發(fā)UnsupportedOperationException,這和JDK的ByteBuffer類似
- 直接緩沖區(qū)
直接緩沖區(qū)允許通過本地調(diào)用來分配內(nèi)存,這種方式可以減少每次調(diào)用本地I/O時將緩沖區(qū)內(nèi)容復(fù)制到中間緩沖區(qū)。
直接緩沖區(qū)缺點在于,它的內(nèi)存分配和釋放比較昂貴,同時因為數(shù)據(jù)不在堆上,所以代碼中不得不多進(jìn)行一次復(fù)制,如下
ByteBuf byteBuf = ...;
if (!byteBuf.hasArray()) {
int length = byteBuf.readableBytes();
byte[] bytes = new byte[length];
byteBuf.getBytes(byteBuf.readerIndex(), bytes);
handle(bytes, 0, length);
}
- 復(fù)合緩沖區(qū)
復(fù)合緩沖區(qū)為多個ByteBuf提供了一個聚合視圖,它允許添加或刪除ByteBuf實例,Netty通過ByteBuf子類---CompositeByteBuf實現(xiàn)這個模式,它提供了一個將多個緩沖區(qū)表示為單個合并緩沖區(qū)的虛擬表示,例子如下
CompositeByteBuf buf = Unpooled.compositeBuffer();
ByteBuf headBuf = ...;
ByteBuf bodyBuf = ...;
buf.addComponents(headBuf, bodyBuf);
for (ByteBuf b:buf) {
System.out.println(b.toString());
}
訪問復(fù)合緩沖區(qū)
CompositeByteBuf buf = Unpooled.compositeBuffer();
int length = buf.readableBytes();
byte[] bytes = new byte[length];
buf.getBytes(buf.readerIndex(), bytes);
handle(bytes, 0, length);
4.ByteBuf的字節(jié)操作
- 隨機(jī)訪問
和Java中的數(shù)組一樣,ByteBuf的索引是從0開始,最后一個字節(jié)的索引是capacity()-1,對ByteBuf的隨機(jī)訪問代碼如下
ByteBuf buf = ...;
for (int i = 0; i < buf.capacity(); i ++) {
byte b = buf.getByte(i);
System.out.println((char)b);
}
需要索引來獲取數(shù)據(jù)的方法不會改變readerIndex、writerIndex的值
- 可讀字節(jié)
對于每個新分配的ByteBuf,其默認(rèn)readerIndex都為0,任何以read或者skip開頭的方法,會檢索或者跳過位于當(dāng)前readerIndex的數(shù)據(jù),并且增加readerIndex的值
ByteBuf buf = ...;
while (buf.isReadable()) {
System.out.println((char)buf.readByte());
}
- 可寫字節(jié)
可寫字段指擁有一段未定義、可寫入的內(nèi)容,新分配的緩沖區(qū)默認(rèn)writerIndex為0,任何以write為開頭的方法都會增加writeIndex的值。
ByteBuf buf = ...;
while (buf.writableBytes() >= 4) {
buf.writeInt(random.nextInt());
}
- 索引管理
通過調(diào)用markReaderIndex、markWriterIndex、resetReaderIndex、resetWriterIndex可以實現(xiàn)對讀、寫索引的標(biāo)記與重置,可以通過readerIndex(int)、writerIndex(int)方法來移動讀、寫索引,注意,任何試圖將索引移動到一個無效位置的操作都會觸發(fā)IndexOutBoundsException。
clear方法會將readerIndex和writerIndex置0,但是不會清除內(nèi)存當(dāng)中的內(nèi)容,它僅僅是改變了索引的值。
- 查找操作
ByteBuf中最簡單的查找方法是indexOf(fromIndex, toIndex, value)方法,這個方法直接返回,這個方法查詢指定索引之間的字符與指定值是否匹配,并返回查找到的索引。ByteBuf還提供了forEachByte()方法,這個方法接收一個ByteBufProcessor的實例,ByteBufProcessor接口只有一個方法:
boolean process(byte value)
通過這種方式可以使用一些較為復(fù)雜的查詢邏輯來進(jìn)行查詢操作,ByteBufProcessor針對一些常見的值定義了一些具體實例,如程序想要查找換行符
ByteBuf buf = ...;
int index = buf.forEach(ByteBufProcessor.FIND_LF);
- 派生緩沖區(qū)
Netty為ByteBuf提供了如下創(chuàng)建視圖的方法
- duplicate()
- slice()
- slice(int, int)
- Unpooled。unmodifiableBuffer();
- order(ByteOrder);
- readSlice(int);
以上每個方法都會返回一個新的ByteBuf實例,他們維護(hù)自己的讀、寫索引,但是內(nèi)部的存儲是共享的,所以對視圖實例的修改也會影響到源實例的內(nèi)容。
如果需要一個ByteBuf的真是副本,可以使用copy()和copy(int, int)方法,這兩個方法會返回一個擁有獨立存儲的ByteBuf。
- 讀寫操作
ByteBuf的讀寫操作與JDK ByteBuffer類似,都提供了getXXX(int)、setXXX(int)的相對給定索引的讀寫方法,如getBoolean(int)、setBoolean(int, boolean)。
而readXXX()和wirteXXX()操作則是相對于readerIndex和writerIndex進(jìn)行讀寫的,此類方法會修改readerIndex和writerIndex的值,如writeBoolean(boolean)、readBoolean()。
- 其他操作
| 名稱 | 描述 |
|---|---|
| isReadable() | 如果有可讀的字節(jié),返回true |
| isWritable() | 如果有可寫的空間,返回true |
| readableBytes() | 返回可讀取的字節(jié)數(shù) |
| writableBytes() | 返回可以寫入的字節(jié)數(shù) |
5.ByteBuf分配
ByteBuf分配分為兩種,一種是ByteBufAllocator進(jìn)行分配,另外一種通過Unpooled進(jìn)行分配,ByteBufAllocator的實例可以通過一下兩種方式獲取
Channel channel = ...;
ByteAllocator allocator1 = channel.alloc();
ChannelHandlerContext ctx = ...;
ByteAllocator allocator2 = ctx.alloc();
ByteBufAllocator提供了一系列重載的buffer()、heapBuffer()、directBuffer()、compositeBuffer()方法來分配ByteBuf。
在某些情況下可能無法獲取到一個ByteAllocator的實例,此時可以使用Unpooled工具類來創(chuàng)建ByteBuf,具體方法如下
- buffer()
- buffer(int initialCapacity)
- buffer(int initialCapacity, int maxCapacity)
- directBuffer()
- directBuffer(int initialCapacity)
- directBuffer(int initialCapacity, int maxCapacity)
- wrappedBuffer(...)
- copiedBuffer(...)