首先,咱們先來了解一下 ByteBuf 的結構數組
以上就是一個 ByteBuf 的結構圖,從上面這幅圖能夠看到:微信
Netty 使用 ByteBuf 這個數據結構能夠有效地區分可讀數據和可寫數據,讀寫之間相互沒有衝突,固然,ByteBuf 只是對二進制數據的抽象,具體底層的實現咱們在下面的小節會講到,在這一小節,咱們 只須要知道 Netty 關於數據讀寫只認 ByteBuf,下面,咱們就來學習一下 ByteBuf 經常使用的 API數據結構
capacity()jvm
表示 ByteBuf 底層佔用了多少字節的內存(包括丟棄的字節、可讀字節、可寫字節),不一樣的底層實現機制有不一樣的計算方式,後面咱們講 ByteBuf 的分類的時候會講到函數
maxCapacity()學習
表示 ByteBuf 底層最大可以佔用多少字節的內存,當向 ByteBuf 中寫數據的時候,若是發現容量不足,則進行擴容,直到擴容到 maxCapacity,超過這個數,就拋異常指針
readableBytes() 與 isReadable()code
readableBytes() 表示 ByteBuf 當前可讀的字節數,它的值等於 writerIndex-readerIndex,若是二者相等,則不可讀,isReadable() 方法返回 false對象
writableBytes()、 isWritable() 與 maxWritableBytes()blog
writableBytes() 表示 ByteBuf 當前可寫的字節數,它的值等於 capacity-writerIndex,若是二者相等,則表示不可寫,isWritable() 返回 false,可是這個時候,並不表明不能往 ByteBuf 中寫數據了, 若是發現往 ByteBuf 中寫數據寫不進去的話,Netty 會自動擴容 ByteBuf,直到擴容到底層的內存大小爲 maxCapacity,而 maxWritableBytes() 就表示可寫的最大字節數,它的值等於 maxCapacity-writerIndex
readerIndex() 與 readerIndex(int)
前者表示返回當前的讀指針 readerIndex, 後者表示設置讀指針
writeIndex() 與 writeIndex(int)
前者表示返回當前的寫指針 writerIndex, 後者表示設置寫指針
markReaderIndex() 與 resetReaderIndex()
前者表示把當前的讀指針保存起來,後者表示把當前的讀指針恢復到以前保存的值,下面兩段代碼是等價的
// 代碼片斷1 int readerIndex = buffer.readerIndex(); // .. 其餘操做 buffer.readerIndex(readerIndex); // 代碼片斷二 buffer.markReaderIndex(); // .. 其餘操做 buffer.resetReaderIndex();
但願你們多多使用代碼片斷二這種方式,不須要本身定義變量,不管 buffer 看成參數傳遞到哪裏,調用 resetReaderIndex() 均可以恢復到以前的狀態,在解析自定義協議的數據包的時候很是常見,推薦你們使用這一對 API
markWriterIndex() 與 resetWriterIndex()
這一對 API 的做用與上述一對 API 相似,這裏再也不 贅述
本質上,關於 ByteBuf 的讀寫均可以看做從指針開始的地方開始讀寫數據
writeBytes(byte[] src) 與 buffer.readBytes(byte[] dst)
writeBytes() 表示把字節數組 src 裏面的數據所有寫到 ByteBuf,而 readBytes() 指的是把 ByteBuf 裏面的數據所有讀取到 dst,這裏 dst 字節數組的大小一般等於 readableBytes(),而 src 字節數組大小的長度一般小於等於 writableBytes()
writeByte(byte b) 與 buffer.readByte()
writeByte() 表示往 ByteBuf 中寫一個字節,而 buffer.readByte() 表示從 ByteBuf 中讀取一個字節,相似的 API 還有 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 與 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 這裏就不一一贅述了,相信讀者應該很容易理解這些 API
與讀寫 API 相似的 API 還有 getBytes、getByte() 與 setBytes()、setByte() 系列,惟一的區別就是 get/set 不會改變讀寫指針,而 read/write 會改變讀寫指針,這點在解析數據的時候千萬要注意
release() 與 retain()
因爲 Netty 使用了堆外內存,而堆外內存是不被 jvm 直接管理的,也就是說申請到的內存沒法被垃圾回收器直接回收,因此須要咱們手動回收。有點相似於c語言裏面,申請到的內存必須手工釋放,不然會形成內存泄漏。
Netty 的 ByteBuf 是經過引用計數的方式管理的,若是一個 ByteBuf 沒有地方被引用到,須要回收底層內存。默認狀況下,當建立完一個 ByteBuf,它的引用爲1,而後每次調用 retain() 方法, 它的引用就加一, release() 方法原理是將引用計數減一,減完以後若是發現引用計數爲0,則直接回收 ByteBuf 底層的內存。
slice()、duplicate()、copy()
這三個方法一般狀況會放到一塊兒比較,這三者的返回值都是一個新的 ByteBuf 對象
retainedSlice() 與 retainedDuplicate()
相信讀者應該已經猜到這兩個 API 的做用了,它們的做用是在截取內存片斷的同時,增長內存的引用計數,分別與下面兩段代碼等價
// retainedSlice 等價於 slice().retain(); // retainedDuplicate() 等價於 duplicate().retain()
使用到 slice 和 duplicate 方法的時候,千萬要理清內存共享,引用計數共享,讀寫指針不共享幾個概念,下面舉兩個常見的易犯錯的例子
Buffer buffer = xxx; doWith(buffer); // 一次釋放 buffer.release(); public void doWith(Bytebuf buffer) { // ... // 沒有增長引用計數 Buffer slice = buffer.slice(); foo(slice); } public void foo(ByteBuf buffer) { // read from buffer // 重複釋放 buffer.release(); }
這裏的 doWith 有的時候是用戶自定義的方法,有的時候是 Netty 的回調方法,好比 channelRead() 等等
Buffer buffer = xxx; doWith(buffer); // 引用計數爲2,調用 release 方法以後,引用計數爲1,沒法釋放內存 buffer.release(); public void doWith(Bytebuf buffer) { // ... // 增長引用計數 Buffer slice = buffer.retainedSlice(); foo(slice); // 沒有調用 release } public void foo(ByteBuf buffer) { // read from buffer }
想要避免以上兩種狀況發生,你們只須要記得一點,在一個函數體裏面,只要增長了引用計數(包括 ByteBuf 的建立和手動調用 retain() 方法),就必須調用 release() 方法。
以上內容來源於掘金小冊《Netty 入門與實戰:仿寫微信 IM 即時通信系統》,若想得到更多,更詳細的內容,請用微信掃碼訂閱: