在Java NIO相關的組件中,ByteBuffer是除了Selector、Channel之外的另一個很重要的組件,它是直接和Channel打交道的緩沖區,通常場景或是從ByteBuffer寫入Channel,或是從Channel讀入Buffer;
在Netty中,被精心設計的ByteBuf則是Netty貫穿整個開發過程中的核心緩沖區,其簡化了ByteBuffer的操作,同時提供與ByteBuffer互操作api,方便了應用的開發。
關于Java NIO的ByteBuffer請參考:http://www.lxweimin.com/p/49d20a7547f6
1、ByteBuf功能說明
1.1、ByteBuffer的缺點
從功能角度而言,ByteBuffer完全可以滿足NIO編程的需要,但是由于NIO編程的復雜性,ByteBuffer也有其局限性,它的主要缺點如下:
- ByteBuffer長度固定,一旦分配完成,它的容量不能動態擴展和收縮,當需要編碼的POJO對象大于ByteBuffer的容量時,會發生索引越界異常;
- ByteBuffer只有一個標識位置的指針position,讀寫的時候需要手工調用flip()和rewind()等,使用者必須小心謹慎地處理這些API,否則很容易導致程序處理失敗;
- ByteBuffer的API功能有限,一些高級和實用的特性它不支持,需要使用者自己編程實現。
為了彌補這些不足,Netty提供了自己的ByteBuffer實現——ByteBuf。
1.2、ByteBuf原理
ByteBuf通過以下位置指針以簡化緩沖區操作:
- readerIndex:讀操作指針,標識讀起始位置;
- writerIndex:寫操作指針,標識寫起始位置;
- maxCapacity:緩沖區容量指針,標識緩沖區的容量大小;
- markedReaderIndex:讀標記指針,記錄readerIndex值;
- markedWriterIndex:讀標記指針,記錄writterIndex值;
readerIndex和writerIndex的取值一開始都是0,隨著數據的寫入writerIndex會增加,讀取數據會使readerIndex增加,但是它不會超過writerIndex。
在讀取之后,0~readerIndex的就被視為discard的,調用discardReadBytes()方法,可以釋放這部分空間,它的作用類似ByteBufferd的compact()方法。readerIndex和writerIndex之間的數據是可讀取的,等價于ByteBuffer的position和limit之間的數據。writerIndex和capacity之間的空間是可寫的,等價于ByteBuffer的limit和capacity之間的可用空間。
由于寫操作不修改readerIndex指針,讀操作不修改writerIndex指針,因此讀寫之間不再需要調整位置指針,這極大地簡化了緩沖區的讀寫操作,避免了由于遺漏或者不熟悉flip()操作導致的功能異常。
初始分配的ByteBuf如圖:
寫入N字節后的ByteBuf如圖:
讀取M(M<N)字節后的ByteBuf如圖:
調用discarReadBytes()之后的ByteBuf如圖:
調用clear()操作之后的ByteBuf如圖:
1.3、動態擴容
當我們對ByteBuffer進行put操作的時候,如果緩沖區剩余可寫空間不夠,就會發生 BufferOverflowException異常。為了避免發生這個問題,通常在進行put操作的時候會對剩余可用空間進行校驗,如果剩余空間不足,需要重新創建一個新的ByteBuffer,并將之前的ByteBuffer復制到新創建的ByteBuffer中,最后釋放老的ByteBuffer。
if(this.buffer.remaining() < needSize) {
int toBeExtSize = needSize < 128 ? needSize : 128;
ByteBuffer tmpBuffer = ByteBuffer.allocate(this.buffer.capacity() + toBeExtSize);
this.buffer.flip();
tmpBuffer.put(this.buffer);
this.buffer = tmpBuffer;
}
從示例代碼可以看出,為了防止ByteBuffer溢出,每進行一次put操作,都需要對可用空間進行校驗,這導致了代碼冗余,稍有不慎,就可能引入其他問題。為了解決這個問題,ByteBuf對write操作進行了封裝,由ByteBuf的write操作負責進行剩余可用空間的校驗,如果可用緩沖區不足,ByteBuf會自動進行動態擴展,對于使用者而言,不需要關心底層的校驗和擴展細節,只要不超過設置的最大緩沖區容量即可。當可用空間不足時,ByteBuf會幫助我們實現自動擴展。
@Override
public ByteBuf ensureWritable(int minWritableBytes) {
if (minWritableBytes < 0) {
throw new IllegalArgumentException(String.format(
"minWritableBytes: %d (expected: >= 0)", minWritableBytes));
}
ensureWritable0(minWritableBytes);
return this;
}
final void ensureWritable0(int minWritableBytes) {
ensureAccessible();
if (minWritableBytes <= writableBytes()) {
return;
}
if (minWritableBytes > maxCapacity - writerIndex) {
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
// Normalize the current capacity to the power of 2.
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
// Adjust to the new capacity.
capacity(newCapacity);
}
通過源碼分析,我們發現當進行write操作時會對需要write的字節進行校驗,如果可寫的字節數小于需要寫入的字節數,并且需要寫入的字節數小于可寫的最大字節數時,對緩沖區進行動態擴展。無論緩沖區是否進行了動態擴展,從功能角度看使用者并不感知,這樣就簡化了上層的應用。
1.4、ByteBuf操作介紹
1.4.1、順序讀操作(read):
方法名稱 | 返回值 | 功能說明 | 拋出異常 |
---|---|---|---|
readBoolean() | boolean | 從readerIndex開始讀取1字節的數據 | throwsIndexOutOfBoundsExceptionreadableBytes<1 |
readByte() | byte | 從readerIndex開始讀取1字節的數據 | throws IndexOutOfBoundsExceptionreadableBytes<1 |
readUnsignedByte() | short | 從readerIndex開始讀取1字節的數據(無符號字節值) | throws IndexOutOfBoundsException:readableBytes<1 |
readShort() | short | 從readerIndex開始讀取16位的短整形值 | throws IndexOutOfBoundsException:readableBytes<2 |
readUnsignedShort() | int | 從readerIndex開始讀取16位的無符號短整形值 | throws IndexOutOfBoundsException:readableBytes<2 |
readMedium() | int | 從readerIndex開始讀取24位的整形值,(該類型并非java基本類型,通常不用) | throws IndexOutOfBoundsException:readableBytes<3 |
readUnsignedMedium() | int | 從readerIndex開始讀取24位的無符號整形值,(該類型并非java基本類型,通常不用) | throws IndexOutOfBoundsException:readableBytes<3 |
readInt() | int | 從readerIndex開始讀取32位的整形值 | throws IndexOutOfBoundsException:readableBytes<4 |
readUnsignedInt() | long | 從readerIndex開始讀取32位的無符號整形值 | throws IndexOutOfBoundsException:readableBytes<4 |
readLong() | long | 從readerIndex開始讀取64位的整形值 | throws IndexOutOfBoundsException:readableBytes<8 |
readChar() | char | 從readerIndex開始讀取2字節的字符值 | throws IndexOutOfBoundsException:readableBytes<2 |
readFloat() | float | 從readerIndex開始讀取32位的浮點值 | throws IndexOutOfBoundsException:readableBytes<4 |
readDouble() | double | 從readerIndex開始讀取64位的浮點值 | throws IndexOutOfBoundsException:readableBytes<8 |
readBytes(int length) | ByteBuf | 將當前ByteBuf中的數據讀取到新創建的ByteBuf中,從readerIndex開始讀取length字節的數據。返回的ByteBuf readerIndex 為0,writeIndex為length。 | throws IndexOutOfBoundsException:readableBytes<length |
readSlice(int length) | ByteBuf | 返回當前ByteBuf新創建的子區域,子區域和原ByteBuf共享緩沖區的內容,但獨立維護自己的readerIndex和writeIndex,新創建的子區域readerIndex 為0,writeIndex為length。 | throws IndexOutOfBoundsException:readableBytes<length |
readBytes(ByteBuf dst) | ByteBuf | 將當前ByteBuf中的數據讀取到目標ByteBuf (dst)中,從當前ByteBuf readerIndex開始讀取,直到目標ByteBuf無可寫空間,從目標ByteBuf writeIndex開始寫入數據。讀取完成后,當前ByteBuf的readerIndex+=讀取的字節數。目標ByteBuf的writeIndex+=讀取的字節數。 | throws IndexOutOfBoundsException:this.readableBytes<dst.writableBytes |
readBytes(ByteBuf dst, int length) | ByteBuf | 將當前ByteBuf中的數據讀取到目標ByteBuf (dst)中,從當前ByteBuf readerIndex開始讀取,長度為length,從目標ByteBuf writeIndex開始寫入數據。讀取完成后,當前ByteBuf的readerIndex+=length,目標ByteBuf的writeIndex+=length | throws IndexOutOfBoundsException:this.readableBytes<length ordst.writableBytes<length |
readBytes(ByteBuf dst, int dstIndex, int length) | ByteBuf | 將當前ByteBuf中的數據讀取到目標ByteBuf (dst)中,從readerIndex開始讀取,長度為length,從目標ByteBuf dstIndex開始寫入數據。讀取完成后,當前ByteBuf的readerIndex+=length,目標ByteBuf的writeIndex+=length | throws IndexOutOfBoundsException:dstIndex<0 orthis.readableBytes<length ordst.capacity<dstIndex + length |
readBytes(byte[] dst) | ByteBuf | 將當前ByteBuf中的數據讀取到byte數組dst中,從當前ByteBuf readerIndex開始讀取,讀取長度為dst.length,從byte數組dst索引0處開始寫入數據。 | throws IndexOutOfBoundsException:this.readableBytes<dst.length |
readBytes(byte[] dst, int dstIndex, int length) | ByteBuf | 將當前ByteBuf中的數據讀取到byte數組dst中,從當前ByteBuf readerIndex開始讀取,讀取長度為length,從byte數組dst索引dstIndex處開始寫入數據。 | throws IndexOutOfBoundsException:dstIndex<0 or this.readableBytes<length or dst.length<dstIndex + length |
readBytes(ByteBuffer dst) | ByteBuf | 將當前ByteBuf中的數據讀取到ByteBuffer dst中,從當前ByteBuf readerIndex開始讀取,直到dst的位置指針到達ByteBuffer 的limit。讀取完成后,當前ByteBuf的readerIndex+=dst.remaining() | throws IndexOutOfBoundsException:this.readableBytes<dst.remaining() |
readBytes(OutputStream out, int length) | ByteBuf | 將當前ByteBuf readerIndex讀取數據到輸出流OutputStream中,讀取的字節長度為length | throws IndexOutOfBoundsException:this.readableBytes<length throws IOException |
readBytes(GatheringByteChannel out, int length) | int | 將當前ByteBuf readerIndex讀取數到GatheringByteChannel 中,寫入out的最大字節長度為length。GatheringByteChannel為非阻塞Channel,調用其write方法不能夠保存將全部需要寫入的數據均寫入成功,存在半包問題。因此其寫入的數據長度為【0,length】,如果操作成功,readerIndex+=實際寫入的字節數,返回實際寫入的字節數 | throws IndexOutOfBoundsException:this.readableBytes<length throws IOException |
1.4.2、順序寫操作(write):
方法名稱 | 返回值 | 功能說明 | 拋出異常 |
---|---|---|---|
writeBoolean(boolean value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=1 | throws IndexOutOfBoundsException:this.writableBytes<1 |
writeByte(int value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=1 | throws IndexOutOfBoundsException:this.writableBytes<1 |
writeShort(int value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=2 | throws IndexOutOfBoundsException:this.writableBytes<2 |
writeMedium(int value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=3 | throws IndexOutOfBoundsException:this.writableBytes<3 |
writeInt(int value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=4 | throws IndexOutOfBoundsException:this.writableBytes<4 |
writeLong(long value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=8 | throws IndexOutOfBoundsException:this.writableBytes<8 |
writeChar(int value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=2 | Throws IndexOutOfBoundsException:this.writableBytes<2 |
writeFloat(float value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=4 | throws IndexOutOfBoundsException:this.writableBytes<4 |
writeDouble(double value) | ByteBuf | 將value寫入到當前ByteBuf中。寫入成功,writeIndex+=8 | throws IndexOutOfBoundsException:this.writableBytes<8 |
writeBytes(ByteBuf src) | ByteBuf | 將源ByteBuf src中從readerIndex開始的所有可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=src.readableBytes | throws IndexOutOfBoundsException:this.writableBytes<src.readableBytes |
writeBytes(ByteBuf src, int length) | ByteBuf | 將源ByteBuf src中從readerIndex開始,長度length的可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=length | throws IndexOutOfBoundsException:this.writableBytes<length or src.readableBytes<length |
writeBytes(ByteBuf src, int srcIndex, int length) | ByteBuf | 將源ByteBuf src中從srcIndex開始,長度length的可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=length | throws IndexOutOfBoundsException:srcIndex<0 or this.writableBytes<length or src.capacity<srcIndex + length |
writeBytes(byte[] src) | ByteBuf | 將源字節數組src中所有可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=src.length | throws IndexOutOfBoundsException:this.writableBytes<src.length |
writeBytes(byte[] src, int srcIndex, int length) | ByteBuf | 將源字節數組src中srcIndex開始,長度為length可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=length | throws IndexOutOfBoundsException:srcIndex<0 or this.writableBytes<src.length or src.length<srcIndex + length |
writeBytes(ByteBuffer mignsrc) | ByteBuf | 將源ByteBuffer src中所有可讀字節寫入到當前ByteBuf。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=src.remaining() | throws IndexOutOfBoundsException:this.writableBytes<src.remaining() |
writeBytes(InputStream in, int length) | int | 將源InputStream in中的內容寫入到當前ByteBuf,寫入的最大長度為length,實際寫入的字節數可能少于length。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=實際寫入的字節數。返回實際寫入的字節數 | throws IndexOutOfBoundsException:this.writableBytes<length |
writeBytes(ScatteringByteChannel in, int length) | int | 將源ScatteringByteChannel in中的內容寫入到當前ByteBuf,寫入的最大長度為length,實際寫入的字節數可能少于length。從當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=實際寫入的字節數。返回實際寫入的字節數 | throws IndexOutOfBoundsException:this.writableBytes<length |
writeZero(int length) | ByteBuf | 將當前緩沖區的內容填充為NUL(0x00),當前ByteBuf writeIndex寫入數據。寫入成功,writeIndex+=length | throws IndexOutOfBoundsException: this.writableBytes<length |
1.4.3、readerIndex 和 writeIndex
ByteBuf提供兩個指針用于支持順序讀寫操作:readerIndex用于標識讀取索引,witterIndex用于標識寫入索引。兩個位置指針將ByteBuf緩沖區分割成三個區域。
作來重用這部分空間,以節約內存,防止ByteBuf動態擴張。這在私有協議棧消息解碼是非常有用,因TCP底層粘包,多個整包消息被TCP粘包作物一個整包發送。通過discardReadBytes()操作可以重用之前已經解碼過的緩沖區,從而防止接收緩沖區因容量不足導致的擴展。但discardReadBytes()操作是把雙刃劍,不能濫用。
方法名稱 | 返回值 | 功能說明 | 拋出異常 |
---|---|---|---|
readerIndex() | int | 返回當前ByteBuf的readerIndex | |
readerIndex(int readerIndex) | ByteBuf | 修改當前ByteBuf的readerIndex | throws IndexOutOfBoundsException this.writerIndex<readerIndex |
writerIndex() | int | 返回當前ByteBuf的writeIndex | |
writerIndex(int writerIndex) | ByteBuf | 修改當前ByteBuf的writeIndex | throws IndexOutOfBoundsException writeIndex<this.readerIndex or this.capacity<writerIndex |
readableBytes() | int | 獲取當前ByteBuf的可讀字節數 this.writerIndex -this.readerIndex | |
writableBytes() | int | 獲取當前ByteBuf的可寫字節數 this.capacity - this.writerIndex | |
setIndex(int readerIndex, int writerIndex) | ByteBuf | 快捷設置當前ByteBuf的readerIndex和writerIndex | throws IndexOutOfBoundsException readerIndex<0 or this.writerIndex<readerIndex or this.capacity<writerIndex |
skipBytes(int length) | ByteBuf | 更新當前ByteBuf的readerIndex,更新后將跳過length字節的數據讀取。 | throws IndexOutOfBoundsException this.readableBytes<length |
1.4.4、discardReadBytes()和clear操作
相比其他操作,緩沖區分配和釋放是比較耗時的操作,因此,我們需要盡量重用它們。而緩沖區的動態擴張需要字節數組的復制,其是個耗時的操作。因此為最大程度提升性能,需要盡最大努力提升緩沖區的重用率。
例如:
緩沖區包含N個整包消息,每個消息長度L,緩沖區可寫字節數為R。當讀取M個整包消息后,如果不對ByteBuf做壓縮或discardReadBytes()操作,則可寫緩沖區長度依然為R。如果做discardReadBytes()操作,則可寫字節數變為R=(R+M*L),之前已經讀取的M個整包的空間會被重用。
需要注意的是,discardReadBytes()操作會發生字節數組的內存復制,故頻繁調用會導致性能下降,因此要注意調用場景。
至于clear()操作,正如JDK的ByteBuffer的clear()一樣,它并不會清空緩沖區內容本身。其主要通過還原readerIndex和writterIndex的值來達到清空緩沖區的效果。
方法名稱 | 返回值 | 功能說明 |
---|---|---|
discardReadBytes() | ByteBuf | 釋放0到readerIndex之間已經讀取的空間;同時復制readerIndex和writerIndex之間的數據到0到writerIndex-readerIndex之間;修改readerIndex和writerIndex的值。該操作會發生字節數據的內存復制,頻繁調用會導致性能下降。此外,相比其他java對象,緩沖區的分配和釋放是個耗時的操作,緩沖區的動態擴張需要進行進行字節數據的復制,也是耗時的操作,因此應盡量提高緩沖區的重用率 |
discardSomeReadBytes() | ByteBuf | 功能和discardReadBytes()相似,不同之處在于可定制要釋放的空間,依賴于具體實現 |
clear() | ByteBuf | 與JDK 的ByteBuffer clear操作相同,該操作不會清空緩沖區內容本身,其主要是為了操作位置指針,將readerIndex和writerIndex重置為0 |
1.4.5、Readable bytes和Writable bytes
readable bytes區段是數據的實際存儲區域,已read和skip開頭的任何操作都將從readIndex開始讀取或者跳過指定的數據,操作完成之后readIndex增加了讀取或跳過的字節數長度。若讀取字節數長度大于實際可讀的字節數,則拋出IndexOutOfBoundException。當出現分配、包裝或復制一個新的ByteBuf對象時,它的readerIndex為0。
writable bytes區段是尚未被使用的空閑空間,任何write開頭的操作都會從writerIndex開始向空閑空間寫入,操作完成后writeIndex增加寫入的字節長度。如果寫入字節長度大于可寫的字節數,則拋出IndexOutOfBoundException異常。新分配、包裝器或復制一個ByteBuf時,其writterIndex為0。
方法名稱 | 返回值 | 功能說明 |
---|---|---|
readableBytes() | int | 獲取可讀的字節數,其等效于(writerIndex - readerIndex) |
writableBytes() | int | 獲取可寫的字節數,其等效于(capacity - waiterIndex) |
maxWritableBytes() | int | 獲取最大可讀取字節數,其等效于(maxCapacity - writterIndex) |
1.4.6、Mark和Reset
當對緩沖區進行讀寫操作時,可能需要對之前的操作進行回滾。ByteBuf可通過調用mark操作將當前的位置指針備份到mark變量中,調用rest操作后,重新將指針的當前位置恢復為備份在mark變量的值。ByteBuf主要有以下相關方法:
- markReaderIndex():將當前的readerIndex備份到markedReaderIndex中;
- resetReaderIndex():將當前的readerIndex重置為markedReaderIndex的值;
- markWriterIndex() :將當前的writerIndex備份到markedWriterIndex中;
- resetWriterIndex():將當前的writerIndex重置為markedWriterIndex的值;
1.4.7、查找操作
ByteBuf提供多種查找方法用于滿足不同應用場景,細分如下:
方法名稱 | 返回值 | 功能說明 | 拋出異常 |
---|---|---|---|
indexOf(int fromIndex, int toIndex, byte value) | int | 從當前ByteBuf中查找首次出現value的位置,fromIndex<=查找范圍<toIndex;查找成功返回位置索引,否則返回-1 | |
bytesBefore(byte value) | int | 從當前ByteBuf中查找首次出現value的位置,readerIndex<=查找范圍<writerIndex;查找成功返回位置索引,否則返回-1 | |
bytesBefore(int length, byte value) | int | 從當前ByteBuf中查找首次出現value的位置,readerIndex<=查找范圍<readerIndex+length;查找成功返回位置索引,否則返回-1 | IndexOutOfBoundsException:this.readableBytes<length |
bytesBefore(int index, int length, byte value) | int | 從當前ByteBuf中查找首次出現value的位置,index<=查找范圍<index+length;查找成功返回位置索引,否則返回-1 | IndexOutOfBoundsException:this.readableBytes<index+length |
forEachByte(ByteBufProcessor processor); | int | 遍歷當前ByteBuf的可讀字節數組,與ByteBufProcessor中設置的查找條件進行比對,從readerIndex開始遍歷直到writerIndex。如果滿足條件,返回位置索引,否則返回-1 | |
forEachByte(int index, int length, ByteBufProcessor processor) | void | 遍歷當前ByteBuf的可讀字節數組,與ByteBufProcessor中設置的查找條件進行比對,從index開始遍歷直到index+length。如果滿足條件,返回位置索引,否則返回-1 | |
forEachByteDesc(ByteBufProcessor processor) | 逆序遍歷當前ByteBuf的可讀字節數組,與ByteBufProcessor中設置的查找條件進行比對,從writerIndex-1開始遍歷直到readerIndex。如果滿足條件,返回位置索引,否則返回-1 | ||
forEachByteDesc(int index, int length, ByteBufProcessor processor) | 逆序遍歷當前ByteBuf的可讀字節數組,與ByteBufProcessor中設置的查找條件進行比對,從index+length-1開始遍歷直到index。如果滿足條件,返回位置索引,否則返回-1 |
對應查找字節,存在一些常用值,如回車換行等,Netty在ByteBufProcessor接口中對這些常用查找字符進行了抽象:
ByteBufProcessor | 功能說明 |
---|---|
FIND_NUL | NUL(0x00),查找是否為空字節 |
FIND_NON_NUL | 查找是否為非空字節 |
FIND_CR | CR('\r'),查找是否為回車符 |
FIND_NON_CR | 查找是否為非回車符 |
FIND_LF | LF('\n'),查找是否為換行符 |
FIND_NON_LF | 查找是否為非換行符 |
FIND_CRLF | CR('\r')或LF('\n'),回車或換行 |
FIND_NON_CRLF | |
FIND_LINEAR_WHITESPACE | ' '或'\t',查找是否為空字符或換行符 |
FIND_NON_LINEAR_WHITESPACE |
1.4.8、Buffer視圖操作
Derived Buffers類似于數據庫視圖,ByteBuf提供了多個接口用于創建某個ByteBuf的視圖或者復制ByteBuf。
主要操作如下:
方法名稱 | 返回值 | 功能說明 |
---|---|---|
duplicate() | ByteBuf | 返回當前ByteBuf的復制對象,復制后的ByteBuf對象與當前ByteBuf對象共享緩沖區的內容,但是維護自己獨立的readerIndex和writerIndex。該操作不修改原ByteBuf的readerIndex和writerIndex。 |
copy() | ByteBuf | 從當前ByteBuf復制一個新的ByteBuf對象,復制的新對象緩沖區的內容和索引均是獨立的。該操作不修改原ByteBuf的readerIndex和writerIndex。(復制readerIndex到writerIndex之間的內容,其他屬性與原ByteBuf相同,如maxCapacity,ByteBufAllocator) |
copy(int index, int length) | ByteBuf | 從當前ByteBuf 指定索引index開始,字節長度為length,復制一個新的ByteBuf對象,復制的新對象緩沖區的內容和索引均是獨立的。該操作不修改原ByteBuf的readerIndex和writerIndex。(其他屬性與原ByteBuf相同,,如maxCapacity,ByteBufAllocator) |
slice() | ByteBuf | 返回當前ByteBuf的可讀子區域,起始位置從readerIndex到writerIndex,返回的ByteBuf對象與當前ByteBuf對象共享緩沖區的內容,但是維護自己獨立的readerIndex和writerIndex。該操作不修改原ByteBuf的readerIndex和writerIndex。返回ByteBuf對象的長度為readableBytes() |
slice(int index, int length) | ByteBuf | 返回當前ByteBuf的可讀子區域,起始位置從index到index+length,返回的ByteBuf對象與當前ByteBuf對象共享緩沖區的內容,但是維護自己獨立的readerIndex和writerIndex。該操作不修改原ByteBuf的readerIndex和writerIndex。返回ByteBuf對象的長度為length |
1.4.9、轉換為JDK ByteBuffer
當通過NIO的SocketChannel進行網絡讀寫時,操作的對象為JDK的ByteBuffer,因此須在接口層支持netty ByteBuf到JDK的ByteBuffer的相互轉換。
將ByteBuf轉換為java.nio.ByteBuffer的方法主要有如下兩個:
方法名稱 | 返回值 | 功能說明 | 拋出異常 |
---|---|---|---|
nioBuffer() | ByteBuffer | 將當前ByteBuf的可讀緩沖區(readerIndex到writerIndex之間的內容)轉換為ByteBuffer,兩者共享共享緩沖區的內容。對ByteBuffer的讀寫操作不會影響ByteBuf的讀寫索引。注意:ByteBuffer無法感知ByteBuf的動態擴展操作。ByteBuffer的長度為readableBytes() | UnsupportedOperationException |
nioBuffer(int index, int length) | ByteBuffer | 將當前ByteBuf的可讀緩沖區(index到index+length之間的內容)轉換為ByteBuffer,兩者共享共享緩沖區的內容。對ByteBuffer的讀寫操作不會影響ByteBuf的讀寫索引。注意:ByteBuffer無法感知ByteBuf的動態擴展操作。ByteBuffer的長度為length | UnsupportedOperationException |
1.4.10、隨機讀寫(set和get)
除順序讀寫之外,ByteBuf還支持隨機讀寫,其最大的區別在于可隨機指定讀寫的索引位置。關于隨機讀寫的API這里不再詳述。無論set或get,執行前都會進行索引和長度的合法性驗證,此外,set操作不同于write操作,set不支持動態擴展,所以使用者必須保證當前緩沖區可寫的字節數大于需要寫入的字節長度,否則會拋出緩沖區越界異常。
隨機讀操作:
隨機寫操作:
2、ByteBuf實現類分析
2.1、ByteBuf類繼承圖
- ReferenceCounted:對象引用計數器,初始化ReferenceCounted對象時,引用數量refCnt為1,調用retain()可增加refCnt,release()用于減少refCnt。refCnt為1時,說明對象實際不可達,release()方法將立即調用deallocate()釋放對象。如果refCnt為0,說明對象被錯誤的引用。在AbstractReferenceCountedByteBuf源碼分析小節將詳細介紹ReferenceCounted的原理。
- ByteBuf:實現接口ReferenceCounted和Comparable,實現ReferenceCounted使得ByteBuf具備引用計數的能力,方便跟蹤ByteBuf對象分配和釋放。
ByteBuf直接子類:
- EmptyByteBuf:用于構建空ByteBuf對象,capacity和maxCapacity均為0。
- WrappedByteBuf:用于裝飾ByteBuf對象,主要有AdvancedLeakAwareByteBuf、SimpleLeakAwareByteBuf、UnreleasableByteBuf和Component四個子類。這里WrappedByteBuf使用裝飾者模式裝飾ByteBuf對象,AdvancedLeakAwareByteBuf用于對所有操作記錄堆棧信息,方便監控內存泄漏;SimpleLeakAwareByteBuf只記錄order(ByteOrder endianness)的堆棧信息;UnreleasableByteBuf用于阻止修改對象引用計數器refCnt的值。
- AbstractByteBuf:提供ByteBuf的默認實現,同時組合ResourceLeakDetector和SwappedByteBuf的能力,ResourceLeakDetector是內存泄漏檢測工具,SwappedByteBuf用于字節序不同時轉換字節序。
AbstractReferenceCountedByteBuf直接子類:
- CompositeByteBuf:用于將多個ByteBuf組合在一起,形成一個虛擬的ByteBuf對象,支持讀寫和動態擴展。內部使用List<Component>組合多個ByteBuf。推薦使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工廠方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)創建CompositeByteBuf對象。
- FixedCompositeByteBuf:用于將多個ByteBuf組合在一起,形成一個虛擬的只讀ByteBuf對象,不允許寫入和動態擴展。內部使用Object[]將多個ByteBuf組合在一起,一旦FixedCompositeByteBuf對象構建完成,則不會被更改。
- PooledByteBuf<T>:基于內存池的ByteBuf,主要為了重用ByteBuf對象,提升內存的使用效率;適用于高負載,高并發的應用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三個子類,PooledDirectByteBuf是在堆外進行內存分配的內存池ByteBuf,PooledHeapByteBuf是基于堆內存分配內存池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外進行內存分配的內存池ByteBuf,區別在于PooledUnsafeDirectByteBuf內部使用基于PlatformDependent相關操作實現ByteBuf,具有平臺相關性。
- ReadOnlyByteBufferBuf:只讀ByteBuf,內部持有ByteBuffer對象,相關操作委托給ByteBuffer實現,該ByteBuf限內部使用,ReadOnlyByteBufferBuf還有一個子類ReadOnlyUnsafeDirectByteBuf。
- UnpooledDirectByteBuf:在堆外進行內存分配的非內存池ByteBuf,內部持有ByteBuffer對象,相關操作委托給ByteBuffer實現。
- UnpooledHeapByteBuf:基于堆內存分配非內存池ByteBuf,即內部持有byte數組。
- UnpooledUnsafeDirectByteBuf:與UnpooledDirectByteBuf相同,區別在于UnpooledUnsafeDirectByteBuf內部使用基于PlatformDependent相關操作實現ByteBuf,具有平臺相關性。
從內存分配角度看,ByteBuf主要分為兩類:
- 堆內存(HeapByteBuf)字節緩沖區:特點是內存的分配和回收速度快,可以被JVM自動回收;缺點是進行Socket的I/O讀寫需要額外進行一次內存復制,即將內存對應的緩沖區復制到內核Channel中,性能會有一定程度下降。
- 直接內存(DirectByteBuf)字節緩沖區:在堆外進行內存分配,相比堆內存,分配和回收速度稍慢。但用于Socket的I/O讀寫時,少一次內存復制,速度比堆內存字節緩沖區快。
經驗表明,在I/O通信線程的讀寫緩沖區使用DirectByteBuf,后端業務消息的編解碼模塊使用HeapByteBuf,這樣組合可以達到性能最優。
從內存回收角度看,ByteBuf也分為兩類:
- 基于內存池的ByteBuf:優點是可以重用ByteBuf對象,通過自己維護一個內存池,可以循環利用創建的ByteBuf,提升內存的使用效率,降低由于高負載導致的頻繁GC。適用于高負載,高并發的應用中。推薦使用基于內存池的ByteBuf。
- 非內存池的ByteBuf:優點是管理和維護相對簡單。
盡管推薦使用基于內存池的ByteBuf,但內存池的管理和維護更加復雜,使用起來也需要更加謹慎。
2.2、AbstractByteBuf源碼分析
AbstractByteBuf是ByteBuf的抽象實現,其提供了一些基礎屬性和公用方法實現。
2.2.1、主要成員變量
readerIndex:讀索引
writerIndex:寫索引
markedReaderIndex:讀索引標記
markedWriterIndex:寫索引標記
maxCapacity:最大容量
SwappedByteBuf swappedBuf:緩沖區包裝類
在AbstractByteBuf中并未定義ByteBuf緩沖區的實現,其實現留給具體實現類。
2.2.2、讀操作簇
在讀取數據數據前,會對緩沖區可讀空間進行檢驗,如果讀取長度小于0,則拋出IllegalArgumentException異常提示參數非法;如果需要讀取的字節數大于可讀長度,則拋出IndexOutOfBoundsException異常。異常中封裝了詳細信息,使用者可以方便地定位問題。
校驗通過后在讀取設定長度的數據,其具體實現依賴于子類實現。
2.2.3、寫操作簇
首先校驗寫入長度的合法性;
如果寫入的數據長度小于0,則拋出IllegalArgumentException異常;如果寫入長度大于可動態擴展的最大長度,則拋出IndexOutOfBoundsException異常;
當寫入長度大于緩沖區長度而小于可動態擴展的最大長度,則進行動態擴展;動態擴展根據倍增加閥值的方式;
2.2.4、操作索引
設置和讀取相關索引;
2.2.4、重用緩沖區
重用緩沖區是通過discardReadBytes()和discardSomeReadBytes()方法實現。
處理流程如下:
- 如果讀索引為0,表示無可重用緩沖區,則直接返回;
- 如果讀索引大于0且不等于寫索引,說明緩沖區有數據且有可重用空間,調用setBytes(0, this, readerIndex, writerIndex-readerIndex)方法進行緩沖區復制,然后重新設置讀索引為0,寫索引為(writerIndex - readerIndex);
2.2.5、skipBytes
在解碼時,有時需要丟棄非法數據,使用skipBytes()可方便跳過指定長度的數據。
2.3、AbstractReferenceCountedByteBuf
AbstractReferenceCountedByteBuf主要是對引用進行計數,類似JVM內存回收的對象引用計數,用于跟蹤對象的分配和銷毀。
2.3.1、成員變量
refCntUpdater:類型為AtomicIntegerFieldUpdater,通過原子方式對成員變量進行更新等操作,以實現線程安全,消除鎖;
2.3.1、對象引用計數
retain()方法用于引用計數加一,由于可能存在并發場景,所以其通過自旋加CAS操作保證線程安全。
實現源碼如下:
private ByteBuf retain0(final int increment) {
int oldRef = refCntUpdater.getAndAdd(this, increment);
if (oldRef <= 0 || oldRef + increment < oldRef) {
// Ensure we don't resurrect (which means the refCnt was 0) and also that we encountered an overflow.
refCntUpdater.getAndAdd(this, -increment);
throw new IllegalReferenceCountException(oldRef, increment);
}
return this;
}
2.4、UnpooledHeapByteBuf源碼解析
UnpooledHeapByteBuf是基于堆內存進行內存分配的字節緩沖區,沒有基于對象池技術實現,每次調用都會創建一個新的對象;
2.4.1、成員變量
成員變量中定義了一個ByteBufAllocator類型的alloc用來分配堆內存,定義一個byte字節數組用作緩沖區,定義一個ByteBuffer類型的tmpNioBuf用作Netty的ByteBuf到JDK ByteBuffer轉換;
private final ByteBufAllocator alloc;
byte[] array;
private ByteBuffer tmpNioBuf;
實際使用ByteBuffer作為緩沖區也是可行,使用Byte數組根本原因是提升性能和更加敏捷地進行位操作。
2.4.2、動態擴展緩沖區
擴容源碼如下:
@Override
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
int oldCapacity = array.length;
byte[] oldArray = array;
if (newCapacity > oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
setArray(newArray);
freeArray(oldArray);
} else if (newCapacity < oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
int readerIndex = readerIndex();
if (readerIndex < newCapacity) {
int writerIndex = writerIndex();
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
} else {
setIndex(newCapacity, newCapacity);
}
setArray(newArray);
freeArray(oldArray);
}
return this;
}
先判斷新的容量是否大于當前緩沖區的容量,如果大于則進行動態擴容;通過byte[] newArray = new byte[newCapacity]創建新容量的字節數組,然后通過System.arrayCopy()方法進行數據復制,最后通過setArray()方法替換舊的緩沖區數組;
2.5、PooledByteBuf源碼解析
PooledByteBuf是基于內存池的緩沖區,netty自己實現了一套內存池管理,具體實現類有:PooledHeapByteBuf、PooledDirectByteBuf、PooledUnsafeDirectByteBuf、PooledUnsafeHeapByteBuf。
由于內存池管理等方面涉及內容較多,本處不多贅述,詳情請看Netty內存池相關文章:
推薦博客:http://www.lxweimin.com/p/4856bd30dd56
3、ByteBuf相關輔助類
netty提供一些ByteBuf相關的輔助類,以簡化緩沖區相關的使用;
3.1、ByteBufHolder
ByteBufHolder是ByteBuf的容器,在Netty中,它非常有用,例如HTTP協議的請求消息和應答消息都可以攜帶消息體,這個消息體在NIO ByteBuffer中就是個ByteBuffer對象,在Netty中就是ByteBuf對象。由于不同的協議消息體可以包含不同的協議字段和功能,因此,需要對ByteBuf進行包裝和抽象,不同的子類可以有不同的實現。為了滿足這些定制化的需求,Netty抽象出了ByteBufHolder對象,它包含了一個ByteBuf,另外還提供了一些其他實用的方法,使用者繼承ByteBufHolder接口后可以按需封裝自己的實現。
3.1、ByteBufAllocator
ByteBufAllocator是字節緩沖區分配器,按照Netty的緩沖區實現不同,共有兩種不同的分配器:基于內存池的字節緩沖區分配器和普通的字節緩沖區分配器。
類繼承圖如下:
主要功能列表:
方法名稱 | 返回值 | 功能說明 |
---|---|---|
buffer() | ByteBuf | 分配一個字節緩沖區,緩沖區的類型由ByteBufAllocator的實現決定 |
buffer(int initialCapacity) | ByteBuf | 分配一個初始容量initialCapacity的字節緩沖區,緩沖區的類型由ByteBufAllocator的實現類決定 |
buffer(int initialCapacity,int maxCapacity) | ByteBuf | 分配一個初始容量initialCapacity,最大容量為maxCapacity的字節緩沖區,緩沖區的類型由ByteBufAllocator的實現類決定 |
ioBuffer(int initialCapacity,int maxCapacity) | ByteBuf | 分配一個初始容量initialCapacity,最大容量為maxCapacity的direct buffer |
heapBuffer(int initialCapacity,int maxCapacity) | ByteBuf | 分配一個初始容量initialCapacity,最大容量為maxCapacity的heap buffer |
directBuffer(int initialCapacity,int maxCapacity) | ByteBuf | 分配一個初始容量initialCapacity,最大容量為maxCapacity的directbuffer |
compositeBuffer(int maxNumComponent) | CompositeByteBuf | 分配一個最大容量為maxCapacity的CompositeByteBuf,內存類型由ByteBufAllocator的實現類決定 |
isDirectBufferPooled() | boolean | 是否使用了直接內存內存池 |
3.2、CompositeByteBuf
CompositeBytebuf允許將多個Bytebuf的實例組合起來,在內部使用一個Bytebuf進行統一管理。
CompositeBytebuf在某些場景非常有用,例如在協議中一個協議通常會包含多個部分,消息體,消息頭等,他們都是Bytebuf對象,如果我們需要對他進行管理,可以把這些Bytebuf對象都放到CompositeBytebuf中進行統一管理
成員變量:
private final ByteBufAllocator alloc;
private final boolean direct;
private final ComponentList components;
private final int maxNumComponents;
private boolean freed;
CompositeBytebuf定義了一個Component類型的集合,Component為ByteBuf的包裝器實現類,其聚合了ByteBuf對象,維護了在集合中的位置偏移量信息等;
Component源碼實現:
private static final class Component {
final ByteBuf buf;
final int length;
int offset;
int endOffset;
Component(ByteBuf buf) {
this.buf = buf;
length = buf.readableBytes();
}
void freeIfNecessary() {
buf.release(); // We should not get a NPE here. If so, it must be a bug.
}
}
3.2、ByteBufUtil
ByteBufUtil是ByteBuf的一個靜態工具類,其提供了一系列靜態方法用于操作ByteBuf對象。
操作方法如下:
操作方法很豐富,主要包括:
- 返回ByteBuf中可讀字節的十六進制字符串,如hexDump()等;
- 字符串編解碼,如encodeString(),decodeString()等;
- ByteBuf字符串比較;
- ByteBuf緩沖區拷貝等;
相關閱讀:
Netty源碼愫讀(二)Channel相關源碼學習【http://www.lxweimin.com/p/02eac974258e】
Netty源碼愫讀(三)ChannelPipeline、ChannelHandlerContext相關源碼學習【http://www.lxweimin.com/p/be82d0fcdbcc】
Netty源碼愫讀(四)ChannelHandler相關源碼學習【http://www.lxweimin.com/p/6ee0a3b9d73a】
Netty源碼愫讀(五)EventLoop與EventLoopGroup相關源碼學習【http://www.lxweimin.com/p/05096995d296】
Netty源碼愫讀(六)ServerBootstrap相關源碼學習【http://www.lxweimin.com/p/a71a9a0291f3】
參考書籍:
李林鋒《Netty權威指南》第二版
《Netty 實戰(精髓)》
參考博客:
https://www.cnblogs.com/wade-luffy/p/6196481.html
https://my.oschina.net/7001/blog/743240
https://my.oschina.net/7001/blog/742236
https://blog.csdn.net/jeffleo/article/details/69230112
http://www.lxweimin.com/p/c4bd37a3555b