Netty學(xué)習(xí)之ByteBuf
前言
在網(wǎng)絡(luò)中傳輸?shù)幕締卧亲止?jié)byte,雖然在NIO中提供了一個(gè)ByteBuffer作為字節(jié)的容器逛腿,不過(guò)由于ByteBuffer比較難使用,所以Netty自己實(shí)現(xiàn)了一個(gè)荒辕,ByteBuf策泣,并且提供了比較靈活的操作方式以及操作工具,本節(jié)我們將詳細(xì)學(xué)習(xí)這一部分的知識(shí)跟束。
ByteBuf
在Netty中莺奸,數(shù)據(jù)通過(guò)ByteBuf
以及ByteBufHolder
來(lái)進(jìn)行操作,ByteBuf具有眾多優(yōu)秀的特性
- 易于擴(kuò)展
- 通過(guò)內(nèi)置的composite buffer類型可以實(shí)現(xiàn)zero-copy
- 根據(jù)需要擴(kuò)展容量冀宴,類似于JDK中的(StringBuilder)
- 自動(dòng)在讀寫模式進(jìn)行切換灭贷,相比ByteBuffer需要調(diào)用
filp()
更加方便 - 采用讀寫兩個(gè)指針
- 支持鏈?zhǔn)秸{(diào)用
- 支持引用計(jì)數(shù)
- 支持池化技術(shù)
工作原理
ByteBuf可以理解為一個(gè)字節(jié)數(shù)組,并且維護(hù)兩個(gè)不同的指針:讀指針以及寫指針略贮,當(dāng)從ByteBuf中讀取數(shù)據(jù)(read開(kāi)頭的函數(shù))的時(shí)候甚疟,讀指針增加,當(dāng)寫入數(shù)據(jù)(write開(kāi)頭的函數(shù))的時(shí)候逃延,寫指針增加览妖,當(dāng)讀寫指針相同時(shí),表示已經(jīng)沒(méi)有數(shù)據(jù)可以讀取揽祥,繼續(xù)讀取會(huì)拋出IndexOutOfBoundException
讽膏,set以及get開(kāi)頭的函數(shù)不會(huì)影響指針,默認(rèn)的最大容量是Integer.MAX_VALUE
拄丰,當(dāng)寫入超過(guò)容量時(shí)府树,會(huì)觸發(fā)異常俐末。
ByteBuf類型
基于堆的ByteBuf,將數(shù)據(jù)存儲(chǔ)在JVM的堆內(nèi)存中奄侠,并且其內(nèi)部是一個(gè)字節(jié)數(shù)組卓箫,但沒(méi)有進(jìn)行緩存的時(shí)候,可以快速地申請(qǐng)以及回收內(nèi)存垄潮,比較適合處理常規(guī)數(shù)據(jù)丽柿。
基于直接內(nèi)存的ByteBuf,ByteBuf的內(nèi)存空間是通過(guò)直接內(nèi)存申請(qǐng)的(本地方法調(diào)用分配的內(nèi)存)魂挂,好處在于可以避免在發(fā)生I/O調(diào)用的時(shí)候甫题,將數(shù)據(jù)從堆空間拷貝到直接內(nèi)存中(節(jié)省一次拷貝),比較適合于進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)傳輸涂召,由于數(shù)據(jù)沒(méi)有在堆中坠非,所以操作的時(shí)候需要先拷貝到堆空間中(需要手動(dòng)操作),缺點(diǎn)是空間的申請(qǐng)以及回收比較消耗資源果正,而且不受gc的管理炎码。
上面兩個(gè)可以通過(guò)hasArray()
進(jìn)行區(qū)分,基于堆的返回true秋泳,基于直接內(nèi)存的返回false
組合ByteBuf(CompositeByteBuf
)潦闲,提供了多個(gè)ByteBuf的聚合視圖,可以往其中添加或者刪除ByteBuf實(shí)例迫皱,可能包含上面兩種類型的ByteBuf歉闰,所以使用的時(shí)候需要注意。
獲取ByteBuf
通過(guò)ByteBufAllocator
Netty通過(guò)ByteBufAllocator接口卓起,來(lái)提供獲取ByteBuf的操作
buffer();
buffer(initCapacity);
buffer(initCapacity, maxCapacity);
heapBuffer();
heapBuffer(initCapacity);
heapBuffer(initCapacity, maxCapacity);
directBuffer();
directBuffer(..);
directBuffer(.., ..);
compositeBuffer();
compositeBuffer(..);
compositeDirectBuffer();
compositeDirectBuffer(..);
compositeHeapBuffer();
compositeHeapBuffer(..);
ioBuffer(); // for i/o in socket
可以從Channel或者ChannelHanderContext中獲取ByteBuffAllocator實(shí)例
public void testAllocator() {
NioServerSocketChannel channel = new NioServerSocketChannel();
ByteBufAllocator alloc = channel.alloc();
ByteBuf byteBuf = alloc.heapBuffer();
System.out.println(byteBuf.hasArray());
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator = ctx.alloc();
}
同時(shí)和敬,Netty提供了兩種ByteBufAllocator的默認(rèn)實(shí)現(xiàn):PooledByteBufAllocator
以及UnpooledByteBufAllocator
// true表示調(diào)用buffer()時(shí),使用直接內(nèi)存戏阅,false表示堆內(nèi)存
ByteBufAllocator allocator = new UnpooledByteBufAllocator(true);
ByteBufAllocator allocator = new PooledByteBufAllocator(true);
true跟false的區(qū)別僅在于buffer()
昼弟,如果是調(diào)用heapBuffer()
,那還是堆內(nèi)存奕筐,跟true/false無(wú)關(guān)
通過(guò)Unpooled
在有一些情況舱痘,我們沒(méi)有辦法獲取ByteBuffAllocator
,則可以通過(guò)Unpooled
工具來(lái)創(chuàng)建未緩存的ByteBuf
實(shí)例
buffer(); // 基于堆的ByteBuff
buffer(..); // 同上
directBuffer(); // 同上
wrappedBuffer(); // 包裝給定內(nèi)容
copiedBuffer(); // 拷貝給定內(nèi)容
ByteBuf操作
隨機(jī)訪問(wèn)
ByteBuf支持類似于數(shù)組的訪問(wèn)形式离赫,并且其下標(biāo)從0開(kāi)始芭逝,最后一個(gè)Byte為capacity() - 1
ByteBuf buffer = Unpooled.copiedBuffer("hello world".getBytes());
for (int i = 0; i < buffer.capacity(); i++) {
System.out.print((char)buffer.getByte(i));
}
連續(xù)訪問(wèn)
ByteBuf由三個(gè)部分組成,如下圖所示
+----------+------------+----------------+
| | | |
| 已經(jīng)讀取 | 可以讀取 | 可以寫入 |
| | | |
+----------+------------+----------------+
0------readerIndex---writeIndex-----capacity
其中已經(jīng)讀取的數(shù)據(jù)不可能再被讀取(指的是當(dāng)讀指針已經(jīng)移動(dòng)后笆怠,讀指針之前的數(shù)據(jù))铝耻,讀指針與寫指針之間的數(shù)據(jù)則可以被繼續(xù)讀取,寫指針與容量之間的空間可以繼續(xù)寫入。
當(dāng)調(diào)用ByteBuf#discardReadByytes()
后瓢捉,后面的數(shù)據(jù)會(huì)移動(dòng)到前面频丘,使得讀指針歸為0光坝,注意該操作會(huì)比較消耗資源惫搏,一般只在內(nèi)存資源比較緊張的時(shí)候才進(jìn)行該操作钻蹬。
移動(dòng)指針
在ByteBuf中脾猛,存在讀寫指針,所以可以根據(jù)需要移動(dòng)指針页眯,當(dāng)調(diào)用read開(kāi)頭的函數(shù)時(shí)财破,讀指針會(huì)移動(dòng)溢吻,write開(kāi)頭的函數(shù)時(shí)靶壮,write指針會(huì)移動(dòng)怔毛,同時(shí),可以調(diào)用readIndex(int)
將讀指針設(shè)置到指定位置腾降,超過(guò)可讀位置會(huì)拋出異常拣度,writeIndex(int)
同理。
可以調(diào)用clear()
將讀寫指針均設(shè)置為0螃壤,該操作并沒(méi)有清空內(nèi)容抗果,也即數(shù)據(jù)依舊可以讀取出來(lái),該操作比discardReadBytes()
消耗更低奸晴,因?yàn)橹皇侵刂昧酥羔槨?/p>
搜索操作
用于查找某個(gè)字節(jié)的下標(biāo)冤馏,可以使用indexOf()
,也可以使用復(fù)雜的操作ByteBufProcessor#process(byte value)
寄啼,同時(shí)ByteBufProcessor定義了一系列公用操作逮光,如ByteBufProcessor.FIND_CR
、ByteBufProcessor.FIND_LF
衍生操作
衍生操作提供了一些可以從當(dāng)前ByteBuf中獲取ByteBuf的操作辕录,其底層公用一個(gè)ByteBuff睦霎,但是具有自己的讀寫指針
public void testDerived() {
ByteBuf buffer = Unpooled.copiedBuffer("hello world".getBytes());
// 影子拷貝,底層其實(shí)是同一個(gè)
ByteBuf duplicate = buffer.duplicate();
buffer.setByte(0, 'a');
// a
System.out.println((char)duplicate.getByte(0));
// 影子切片走诞,可以帶參數(shù)
ByteBuf slice = buffer.slice();
buffer.setByte(0, 'a');
// a
System.out.println((char) slice.getByte(0));
ByteBuf byteBuf = buffer.readSlice(buffer.readableBytes());
for (int i = 0; i < byteBuf.capacity(); i++) {
System.out.print((char) byteBuf.getByte(i));
}
}
如果是要拷貝數(shù)據(jù),則應(yīng)該使用copy()
或者copy(int, int)
操作
ByteBufUtils
在Netty中蛤高,同時(shí)還提供了ByteBufUtils工具類來(lái)操作ByteBuf蚣旱,如hexDump()
可以用于打印ByteBuf的內(nèi)容,更多關(guān)于ByteBufUtils戴陡,可以參考API即可塞绿。
引用計(jì)數(shù)
Netty為ByteBuf以及ByteBufHolder引入了引用計(jì)數(shù),兩者均實(shí)現(xiàn)了ReferenceCounted
接口恤批,可以用于提高性能异吻。
當(dāng)引用計(jì)數(shù)的值大于0的時(shí)候,對(duì)應(yīng)的資源不會(huì)被釋放,當(dāng)計(jì)數(shù)值等于0時(shí)诀浪,資源會(huì)被釋放掉棋返。
通常來(lái)說(shuō),最后一個(gè)使用資源的對(duì)象需要釋放掉該資源雷猪,即調(diào)用其release()
方法
總結(jié)
本小節(jié)主要詳細(xì)學(xué)習(xí)了Netty中的數(shù)據(jù)容器睛竣,ByteBuf,在Netty中求摇,所有的數(shù)據(jù)都是存放在ByteBuf中射沟,所以,對(duì)Netty中數(shù)據(jù)的操作与境,其實(shí)就是對(duì)ByteBuf的操作验夯,ByteBuf有三種不同的類型,基于堆的摔刁,基于直接內(nèi)存的挥转,組合類型的,在使用的時(shí)候需要根據(jù)情況選擇合適的容器簸搞,同時(shí)扁位,為了提高性能,Netty中引入了引用計(jì)數(shù)的概念趁俊,所以域仇,當(dāng)資源不需要使用的時(shí)候,需要顯示釋放掉對(duì)應(yīng)的資源寺擂。