Netty ByteBuf(图解之 2)| 秒懂
目錄
- Netty ByteBuf(圖解二):API 圖解- 源碼工程
- 寫在前面
- ByteBuf 的四個邏輯部分
- ByteBuf 的三個指針
- ByteBuf 的三組方法
- ByteBuf 的引用計數(shù)
- ByteBuf 的淺層復制
- 寫在最后
- 瘋狂創(chuàng)客圈 Java 死磕系列
 
Netty ByteBuf(圖解二):API 圖解
瘋狂創(chuàng)客圈 Java 分布式聊天室【 億級流量】實戰(zhàn)系列之16 【 博客園 總?cè)肟?/font> 】
源碼工程
源碼IDEA工程獲取鏈接:Java 聊天室 實戰(zhàn) 源碼
寫在前面
? 大家好,我是作者尼恩。
? 今天是百萬級流量 Netty 聊天器 打造的系列文章的第16篇,這是一個基礎篇,介紹ByteBuf 的使用。
? 由于關于ByteBuf的內(nèi)容比較多,分兩篇文章:
? 第一篇:圖解 ByteBuf的分配、釋放和如何避免內(nèi)存泄露
? 第二篇:圖解 ByteBuf的具體使用
? 本篇為第二篇。
ByteBuf 的四個邏輯部分
ByteBuf 是一個字節(jié)容器,內(nèi)部是一個字節(jié)數(shù)組。
從邏輯上來分,字節(jié)容器內(nèi)部,可以分為四個部分:
第一個部分是已經(jīng)丟棄的字節(jié),這部分數(shù)據(jù)是無效的;
第二部分是可讀字節(jié),這部分數(shù)據(jù)是 ByteBuf 的主體數(shù)據(jù), 從 ByteBuf 里面讀取的數(shù)據(jù)都來自這一部分;
第三部分的數(shù)據(jù)是可寫字節(jié),所有寫到 ByteBuf 的數(shù)據(jù)都會寫到這一段。
第四部分的字節(jié),表示的是該 ByteBuf 最多還能擴容的大小。
四個部分的邏輯功能,如下圖所示:
ByteBuf 的三個指針
ByteBuf 通過三個整型的指針(index),有效地區(qū)分可讀數(shù)據(jù)和可寫數(shù)據(jù),使得讀寫之間相互沒有沖突。
這個三個指針,分別是:
- readerIndex(讀指針)
- writerIndex(寫指針)
- maxCapacity(最大容量)
? 這三個指針,是三個int 型的成員屬性,定義在 AbstractByteBuf 抽象基類中。
? 三個指針的代碼截圖,如下:
readerIndex 讀指針
指示讀取的起始位置。
每讀取一個字節(jié),readerIndex 自增1 。一旦 readerIndex 與 writerIndex 相等,ByteBuf 不可讀 。
writerIndex 寫指針
指示寫入的起始位置。
每寫一個字節(jié),writerIndex 自增1。一旦增加到 writerIndex 與 capacity() 容量相等,表示 ByteBuf 已經(jīng)不可寫了 。
capacity()容量不是一個成員屬性,是一個成員方法。表示 ByteBuf 內(nèi)部的總?cè)萘俊?注意,這個不是最大容量。
maxCapacity 最大容量
指示可以 ByteBuf 擴容的最大容量。
當向 ByteBuf 寫數(shù)據(jù)的時候,如果容量不足,可以進行擴容。
擴容的最大限度,直到 capacity() 擴容到 maxCapacity為止,超過 maxCapacity 就會報錯。
capacity()擴容的操作,是底層自動進行的。
ByteBuf 的三組方法
從三個維度三大系列,介紹ByteBuf 的常用 API 方法。
第一組:容量系列
- 方法 一:capacity() - 表示 ByteBuf 的容量,包括丟棄的字節(jié)數(shù)、可讀字節(jié)數(shù)、可寫字節(jié)數(shù)。 
- 方法二:maxCapacity() - 表示 ByteBuf 底層最大能夠占用的最大字節(jié)數(shù)。當向 ByteBuf 中寫數(shù)據(jù)的時候,如果發(fā)現(xiàn)容量不足,則進行擴容,直到擴容到 maxCapacity。 
第二組:寫入系列
- 方法一:isWritable() - 表示 ByteBuf 是否可寫。如果 capacity() 容量大于 writerIndex 指針的位置 ,則表示可寫。否則為不可寫。 - isWritable()的源碼,也是很簡單的。具體如下: 
注意:如果 isWritable() 返回 false,并不代表不能往 ByteBuf 中寫數(shù)據(jù)了。 如果Netty發(fā)現(xiàn)往 ByteBuf 中寫數(shù)據(jù)寫不進去的話,會自動擴容 ByteBuf。
- 方法二:writableBytes() - 返回表示 ByteBuf 當前可寫入的字節(jié)數(shù),它的值等于 capacity()- writerIndex。 - 如下圖所示: 
- 方法三:maxWritableBytes() - 返回可寫的最大字節(jié)數(shù),它的值等于 maxCapacity-writerIndex 。 
- 方法四:writeBytes(byte[] src) - 把字節(jié)數(shù)組 src 里面的數(shù)據(jù)全部寫到 ByteBuf。 - 這個是最為常用的一個方法。 
- 方法五:writeTYPE(TYPE value) 基礎類型寫入方法 - 基礎數(shù)據(jù)類型的寫入,包含了 8大基礎類型的寫入。 - 具體如下:writeByte()、 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() ,向 ByteBuf寫入基礎類型的數(shù)據(jù)。 
- 方法六:setTYPE(TYPE value)基礎類型寫入,不改變指針值 - 基礎數(shù)據(jù)類型的寫入,包含了 8大基礎類型的寫入。 - 具體如下:setByte()、 setBoolean()、setChar()、setShort()、setInt()、setLong()、setFloat()、setDouble() ,向 ByteBuf 寫入基礎類型的數(shù)據(jù)。 - setType 系列與writeTYPE系列的不同: - setType 系列 不會 改變寫指針 writerIndex ; - writeTYPE系列 會 改變寫指針 writerIndex 的值。 
- 方法七:markWriterIndex() 與 resetWriterIndex() - 這里兩個方法一起介紹。 - 前一個方法,表示把當前的寫指針writerIndex 保存在 markedWriterIndex 屬性中; - 后一個方法,表示把當前的寫指針 writerIndex 恢復到之前保存的 markedWriterIndex 值 。 - 標記 markedWriterIndex 屬性, 定義在 AbstractByteBuf 抽象基類中。 - 截圖如下: 
第三組:讀取系列
- 方法一:isReadable() - 表示 ByteBuf 是否可讀。如果 writerIndex 指針的值大于 readerIndex 指針的值 ,則表示可讀。否則為不可寫。 - isReadable()的源碼,也是很簡單的。具體如下: 
- 方法二:readableBytes() - 返回表示 ByteBuf 當前可讀取的字節(jié)數(shù),它的值等于 writerIndex - readerIndex 。 - 如下圖所示: 
- 方法三: readBytes(byte[] dst) - 把 ByteBuf 里面的數(shù)據(jù)全部讀取到 dst 字節(jié)數(shù)組中,這里 dst 字節(jié)數(shù)組的大小通常等于 readableBytes() 。 這個方法,也是最為常用的一個方法。 
- 方法四:readType() 基礎類型讀取 - 基礎數(shù)據(jù)類型的讀取,可以讀取 8大基礎類型。 - 具體如下:readByte()、readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() ,從 ByteBuf讀取對應的基礎類型的數(shù)據(jù)。 
- 方法五:getTYPE(TYPE value)基礎類型讀取,不改變指針值 - 基礎數(shù)據(jù)類型的讀取,可以讀取 8大基礎類型。 - 具體如下:getByte()、 getBoolean()、getChar()、getShort()、getInt()、getLong()、getFloat()、getDouble() ,從 ByteBuf讀取對應的基礎類型的數(shù)據(jù)。 - getType 系列與readTYPE系列的不同: - getType 系列 不會 改變讀指針 readerIndex ; - readTYPE系列 會 改變讀指針 readerIndex 的值。 
- 方法六:markReaderIndex() 與 resetReaderIndex() - 這里兩個方法一起介紹。 - 前一個方法,表示把當前的讀指針ReaderIndex 保存在 markedReaderIndex 屬性中。 - 后一個方法,表示把當前的讀指針 ReaderIndex 恢復到之前保存的 markedReaderIndex 值 。 - 標記 markedReaderIndex 屬性, 定義在 AbstractByteBuf 抽象基類中。 - 截圖如下: 
ByteBuf 的引用計數(shù)
Netty 的 ByteBuf 的內(nèi)存回收工作,是通過引用計數(shù)的方式管理的。
大致的引用計數(shù)的規(guī)則如下:
- 默認情況下,當創(chuàng)建完一個 ByteBuf 時,它的引用為1。
- 每次調(diào)用 retain()方法, 它的引用就加 1 ;
- 每次調(diào)用 release() 方法,是將引用計數(shù)減 1。
如果引用為0,再次訪問這個 ByteBuf 對象,將會拋出異常。
如果引用為0,表示這個 ByteBuf 沒有地方被引用到,需要回收內(nèi)存。
Netty的內(nèi)存回收分為兩種情況:
- Pooled 池化的內(nèi)存,放入可以重新分配的 ByteBuf 池子,等待下一次分配。
- Unpooled 未池化的 ByteBuf 內(nèi)存,確保GC 可達,確保 能被 JVM 的 GC 回收器回收到。
ByteBuf 的淺層復制
ByteBuf 的淺層復制分為兩種,有切片slice 淺層復制,和duplicate 淺層復制。
slice 切片淺層復制
? 首先說明一下,這是一種非常重要的操作。可以很大程度的避免內(nèi)存拷貝。這一點,對于大規(guī)模消息通訊來說,是非常重要的。
? slice 操作可以獲取到一個 ByteBuf 的一個切片。一個ByteBuf,可以進行多次的切片操作,多個切片可以共享一個存儲區(qū)域的 ByteBuf 對象。
? slice 操作方法有兩個重載版本:
- public ByteBuf slice();
- public ByteBuf slice(int index, int length); - 兩個版本有非常緊密的聯(lián)系。 
 不帶參數(shù)的 slice 方法,等同于 buf.slice(buf.readerIndex(), buf.readableBytes()) 調(diào)用, 即返回 ByteBuf 實例中可讀部分的切片。
 而帶參數(shù) slice(int index, int length) 方法,可以通過靈活的設置不同的參數(shù),來獲取到 buf 的不同區(qū)域的切片。
調(diào)用slice()方法后,返回的 ByteBuf 的切片,大致如下圖:
調(diào)用slice()方法后,返回的ByteBuf 切片的屬性,大致如下:
- slice 的 readerIndex(讀指針)的值為 0 
- slice 的 writerIndex(寫指針) 的 值為源Bytebuf的 readableBytes() 可讀字節(jié)數(shù)。 
- slice 的 maxCapacity(最大容量) 的值為源Bytebuf的 readableBytes() 可讀字節(jié)數(shù)。maxCapacity 與 writerIndex 值相同,切片不可以寫。 
- 切片的可讀字節(jié)數(shù),為自己的 writerIndex - readerIndex。所有,切片和源Bytebuf的 readableBytes() 可讀字節(jié)數(shù)相同。 
- 也就是說,切片可讀,不可寫。 
slice()切片和原ByteBuf的聯(lián)系:
- 切片不會拷貝原ByteBuf底層數(shù)據(jù),底層數(shù)組和原ByteBuf的底層數(shù)組是同一個
- 切片不會改變原 ByteBuf 的引用計數(shù)。
根本上,調(diào)用slice()方法生成的切片,是 源Bytebuf 可讀部分的淺層復制。
下面的例子展示了 ByteBuf.slice 方法的演示:
public static void testSlice() {ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100);print("allocate ByteBuf(9, 100)", buffer);buffer.writeBytes(new byte[]{1, 2, 3, 4});print("writeBytes(1,2,3,4)", buffer);ByteBuf buffer1= buffer.slice();print("buffer slice", buffer1); }結(jié)果如下:
after ===========allocate ByteBuf(9, 100)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 0 isReadable(): false writerIndex(): 0 writableBytes(): 9 isWritable(): true maxWritableBytes(): 100after ===========writeBytes(1,2,3,4)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 5 isWritable(): true maxWritableBytes(): 96after ===========buffer slice============ capacity(): 4 maxCapacity(): 4 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 0 isWritable(): false maxWritableBytes(): 0duplicate() 淺層復制
duplicate() 返回的是源ByteBuf 的整個對象的一個淺層復制,包括如下內(nèi)容:
- duplicate() 會創(chuàng)建自己的讀寫指針,但是值與源ByteBuf 的讀寫指針相同;
- duplicate() 不會改變源 ByteBuf 的引用計數(shù)
- duplicate() 不會拷貝 源ByteBuf 的底層數(shù)據(jù)
duplicate() 和slice() 方法,都是淺層復制。不同的是,slice() 方法是切取一段的淺層復制,duplicate() 是整個的淺層復制。
淺層復制的問題
? 淺層復制方法不會拷貝數(shù)據(jù),也不會改變 ByteBuf 的引用計數(shù),這就會導致一個問題。
? 在源 ByteBuf 調(diào)用 release() 之后,引用計數(shù)為零,變得不能訪問。這個時候,源 ByteBuf 的淺層復制實例,也不能進行讀寫。如果再對淺層復制實例進行讀寫,就會報錯。
? 因此,在調(diào)用淺層復制實例時,可以通過調(diào)用一次 retain() 方法 來增加引用,表示它們對應的底層的內(nèi)存多了一次引用,引用計數(shù)為2,在淺層復制實例用完后,需要調(diào)用兩次 release() 方法,將引用計數(shù)減一,不影響源ByteBuf的內(nèi)存釋放。
寫在最后
? 至此為止,終于完成ByteBuf的具體使用B介紹。
? 如果想知道ByteBuf的分配、釋放, 請看:
? 第一篇:ByteBuf的分配、釋放和如何避免內(nèi)存泄露
瘋狂創(chuàng)客圈 Java 死磕系列
- Java (Netty) 聊天程序【 億級流量】實戰(zhàn) 開源項目實戰(zhàn) 
- Netty 源碼、原理、JAVA NIO 原理
- Java 面試題 一網(wǎng)打盡
- 瘋狂創(chuàng)客圈 【 博客園 總?cè)肟?】 
轉(zhuǎn)載于:https://www.cnblogs.com/crazymakercircle/p/9979897.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結(jié)
以上是生活随笔為你收集整理的Netty ByteBuf(图解之 2)| 秒懂的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Mysql8.0.12安装教程方法 My
- 下一篇: python测试开发django-8.w
