目錄html
瘋狂創客圈 Java 分佈式聊天室【 億級流量】實戰系列之16 【 博客園 總入口 】java
源碼IDEA工程獲取連接:Java 聊天室 實戰 源碼面試
你們好,我是做者尼恩。api
今天是百萬級流量 Netty 聊天器 打造的系列文章的第16篇,這是一個基礎篇,介紹ByteBuf 的使用。數組
因爲關於ByteBuf的內容比較多,分兩篇文章:分佈式
第一篇:圖解 ByteBuf的分配、釋放和如何避免內存泄露this
第二篇:圖解 ByteBuf的具體使用.net
本篇爲第二篇。3d
ByteBuf 是一個字節容器,內部是一個字節數組。指針
從邏輯上來分,字節容器內部,能夠分爲四個部分:
第一個部分是已經丟棄的字節,這部分數據是無效的;
第二部分是可讀字節,這部分數據是 ByteBuf 的主體數據, 從 ByteBuf 裏面讀取的數據都來自這一部分;
第三部分的數據是可寫字節,全部寫到 ByteBuf 的數據都會寫到這一段。
第四部分的字節,表示的是該 ByteBuf 最多還能擴容的大小。
四個部分的邏輯功能,以下圖所示:
ByteBuf 經過三個整型的指針(index),有效地區分可讀數據和可寫數據,使得讀寫之間相互沒有衝突。
這個三個指針,分別是:
這三個指針,是三個int 型的成員屬性,定義在 AbstractByteBuf 抽象基類中。
三個指針的代碼截圖,以下:
readerIndex 讀指針
指示讀取的起始位置。
每讀取一個字節,readerIndex 自增1 。一旦 readerIndex 與 writerIndex 相等,ByteBuf 不可讀 。
writerIndex 寫指針
指示寫入的起始位置。
每寫一個字節,writerIndex 自增1。一旦增長到 writerIndex 與 capacity() 容量相等,表示 ByteBuf 已經不可寫了 。
capacity()容量不是一個成員屬性,是一個成員方法。表示 ByteBuf 內部的總容量。 注意,這個不是最大容量。
maxCapacity 最大容量
指示能夠 ByteBuf 擴容的最大容量。
當向 ByteBuf 寫數據的時候,若是容量不足,能夠進行擴容。
擴容的最大限度,直到 capacity() 擴容到 maxCapacity爲止,超過 maxCapacity 就會報錯。
capacity()擴容的操做,是底層自動進行的。
從三個維度三大系列,介紹ByteBuf 的經常使用 API 方法。
方法 一:capacity()
表示 ByteBuf 的容量,包括丟棄的字節數、可讀字節數、可寫字節數。
方法二:maxCapacity()
表示 ByteBuf 底層最大可以佔用的最大字節數。當向 ByteBuf 中寫數據的時候,若是發現容量不足,則進行擴容,直到擴容到 maxCapacity。
方法一:isWritable()
表示 ByteBuf 是否可寫。若是 capacity() 容量大於 writerIndex 指針的位置 ,則表示可寫。不然爲不可寫。
isWritable()的源碼,也是很簡單的。具體以下:
public boolean isWritable() { return this.capacity() > this.writerIndex; }
注意:若是 isWritable() 返回 false,並不表明不能往 ByteBuf 中寫數據了。 若是Netty發現往 ByteBuf 中寫數據寫不進去的話,會自動擴容 ByteBuf。
方法二:writableBytes()
返回表示 ByteBuf 當前可寫入的字節數,它的值等於 capacity()- writerIndex。
以下圖所示:
方法三:maxWritableBytes()
返回可寫的最大字節數,它的值等於 maxCapacity-writerIndex 。
方法四:writeBytes(byte[] src)
把字節數組 src 裏面的數據所有寫到 ByteBuf。
這個是最爲經常使用的一個方法。
方法五:writeTYPE(TYPE value) 基礎類型寫入方法
基礎數據類型的寫入,包含了 8大基礎類型的寫入。
具體以下:writeByte()、 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() ,向 ByteBuf寫入基礎類型的數據。
方法六:setTYPE(TYPE value)基礎類型寫入,不改變指針值
基礎數據類型的寫入,包含了 8大基礎類型的寫入。
具體以下:setByte()、 setBoolean()、setChar()、setShort()、setInt()、setLong()、setFloat()、setDouble() ,向 ByteBuf 寫入基礎類型的數據。
setType 系列與writeTYPE系列的不一樣:
setType 系列 不會 改變寫指針 writerIndex ;
writeTYPE系列 會 改變寫指針 writerIndex 的值。
方法七:markWriterIndex() 與 resetWriterIndex()
這裏兩個方法一塊兒介紹。
前一個方法,表示把當前的寫指針writerIndex 保存在 markedWriterIndex 屬性中;
後一個方法,表示把當前的寫指針 writerIndex 恢復到以前保存的 markedWriterIndex 值 。
標記 markedWriterIndex 屬性, 定義在 AbstractByteBuf 抽象基類中。
截圖以下:
方法一:isReadable()
表示 ByteBuf 是否可讀。若是 writerIndex 指針的值大於 readerIndex 指針的值 ,則表示可讀。不然爲不可寫。
isReadable()的源碼,也是很簡單的。具體以下:
public boolean isReadable() { return this.writerIndex > this.readerIndex; }
方法二:readableBytes()
返回表示 ByteBuf 當前可讀取的字節數,它的值等於 writerIndex - readerIndex 。
以下圖所示:
方法三: readBytes(byte[] dst)
把 ByteBuf 裏面的數據所有讀取到 dst 字節數組中,這裏 dst 字節數組的大小一般等於 readableBytes() 。 這個方法,也是最爲經常使用的一個方法。
方法四:readType() 基礎類型讀取
基礎數據類型的讀取,能夠讀取 8大基礎類型。
具體以下:readByte()、readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() ,從 ByteBuf讀取對應的基礎類型的數據。
方法五:getTYPE(TYPE value)基礎類型讀取,不改變指針值
基礎數據類型的讀取,能夠讀取 8大基礎類型。
具體以下:getByte()、 getBoolean()、getChar()、getShort()、getInt()、getLong()、getFloat()、getDouble() ,從 ByteBuf讀取對應的基礎類型的數據。
getType 系列與readTYPE系列的不一樣:
getType 系列 不會 改變讀指針 readerIndex ;
readTYPE系列 會 改變讀指針 readerIndex 的值。
方法六:markReaderIndex() 與 resetReaderIndex()
這裏兩個方法一塊兒介紹。
前一個方法,表示把當前的讀指針ReaderIndex 保存在 markedReaderIndex 屬性中。
後一個方法,表示把當前的讀指針 ReaderIndex 恢復到以前保存的 markedReaderIndex 值 。
標記 markedReaderIndex 屬性, 定義在 AbstractByteBuf 抽象基類中。
截圖以下:
Netty 的 ByteBuf 的內存回收工做,是經過引用計數的方式管理的。
大體的引用計數的規則以下:
若是引用爲0,再次訪問這個 ByteBuf 對象,將會拋出異常。
若是引用爲0,表示這個 ByteBuf 沒有地方被引用到,須要回收內存。
Netty的內存回收分爲兩種狀況:
ByteBuf 的淺層複製分爲兩種,有切片slice 淺層複製,和duplicate 淺層複製。
首先說明一下,這是一種很是重要的操做。能夠很大程度的避免內存拷貝。這一點,對於大規模消息通信來講,是很是重要的。
slice 操做能夠獲取到一個 ByteBuf 的一個切片。一個ByteBuf,能夠進行屢次的切片操做,多個切片能夠共享一個存儲區域的 ByteBuf 對象。
slice 操做方法有兩個重載版本:
public ByteBuf slice(int index, int length);
兩個版本有很是緊密的聯繫。
不帶參數的 slice 方法,等同於 buf.slice(buf.readerIndex(), buf.readableBytes()) 調用, 即返回 ByteBuf 實例中可讀部分的切片。
而帶參數 slice(int index, int length) 方法,能夠經過靈活的設置不一樣的參數,來獲取到 buf 的不一樣區域的切片。
調用slice()方法後,返回的 ByteBuf 的切片,大體以下圖:
調用slice()方法後,返回的ByteBuf 切片的屬性,大體以下:
slice 的 readerIndex(讀指針)的值爲 0
slice 的 writerIndex(寫指針) 的 值爲源Bytebuf的 readableBytes() 可讀字節數。
slice 的 maxCapacity(最大容量) 的值爲源Bytebuf的 readableBytes() 可讀字節數。maxCapacity 與 writerIndex 值相同,切片不能夠寫。
切片的可讀字節數,爲本身的 writerIndex - readerIndex。全部,切片和源Bytebuf的 readableBytes() 可讀字節數相同。
也就是說,切片可讀,不可寫。
slice()切片和原ByteBuf的聯繫:
根本上,調用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); }
結果以下:
after ===========allocate ByteBuf(9, 100)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 0 isReadable(): false writerIndex(): 0 writableBytes(): 9 isWritable(): true maxWritableBytes(): 100 after ===========writeBytes(1,2,3,4)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 5 isWritable(): true maxWritableBytes(): 96 after ===========buffer slice============ capacity(): 4 maxCapacity(): 4 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 0 isWritable(): false maxWritableBytes(): 0
duplicate() 返回的是源ByteBuf 的整個對象的一個淺層複製,包括以下內容:
duplicate() 和slice() 方法,都是淺層複製。不一樣的是,slice() 方法是切取一段的淺層複製,duplicate() 是整個的淺層複製。
淺層複製方法不會拷貝數據,也不會改變 ByteBuf 的引用計數,這就會致使一個問題。
在源 ByteBuf 調用 release() 以後,引用計數爲零,變得不能訪問。這個時候,源 ByteBuf 的淺層複製實例,也不能進行讀寫。若是再對淺層複製實例進行讀寫,就會報錯。
所以,在調用淺層複製實例時,能夠經過調用一次 retain() 方法 來增長引用,表示它們對應的底層的內存多了一次引用,引用計數爲2,在淺層複製實例用完後,須要調用兩次 release() 方法,將引用計數減一,不影響源ByteBuf的內存釋放。
至此爲止,終於完成ByteBuf的具體使用B介紹。
若是想知道ByteBuf的分配、釋放, 請看:
Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰
瘋狂創客圈 【 博客園 總入口 】