ByteBuf 功能說明
上一篇文章 NIO入門之緩沖區Buffer 已經介紹了 Java 1.4 引入的 java.nio.Buffer
。
從功能角度而言,ByteBuffer 完全可以滿足 NIO 編程的需要,但是由於 NIO 編程的復雜性,ByteBuffer 也有其局限性,它的主要缺點如下:
- ByteBuffer 長度固定,一旦分配完成,它的容量不能動態拓展和收縮,當需要編碼的 POJO 對象大於 ByteBuffer 的容量時,會發生索引越界異常。
ByteBuffer buffer = ByteBuffer.allocate(1024); // 指定分配固定長度的容量
- ByteBuffer 只有一個標識位置的指針 position,讀寫的時候需要手動調用 flip() 和 rewind(),使用必須小心謹慎地處理這些 API,否則很容易導致程序處理失敗;
buffer.clear();
socketChannel.read(buffer); // 讀取前后都要選用合適的 NIO 的 API
buffer.flip();
- ByteBuffer 的 API 功能有限,一些高級和實用的特性它不支持,需要使用者自己編程實現。
為了彌補這些不足,Netty 提供了自己的 ByteBuffer 實現——ByteBuf。
特性1:引入雙標識位置
java.nio.Buffer 只有一個位置指針 position 來處理讀寫操作,因此每次需要讀寫的時候,都需要額外地調用 flip() 和 clear() 方法,否則將可能出現 BufferUnderflowException 或者 BufferOverflowException 異常。
io.netty.buffer.ByteBuf 通過兩個位置指針來協助緩沖區的讀寫操作:
- 讀操作使用 readerIndex
- 寫操作使用 writerIndex
API:io.netty.buffer.ByteBuf
- int writableBytes()
獲取當前的可寫字節數 - int readableBytes()
獲取當前的可讀字節數
剛剛初始化的 ByteBuf 或者調用 clear 之后的 ByteBuf 讀寫指針都是停留在 0 的位置。
當前沒有可讀的字節,如果強行讀取會拋出java.lang.IndexOutOfBoundsException:
- readerIndex 和 writerIndex 之間的數據時可讀取的,對應 java.nio.Buffer 的 position 和 limit 之間的數據。
- writerIndex 和 capacity 之間的空間是可寫的,等價於 Buffer limit 和 capacity 之間的可用空間。
特性2:自動擴容
ByteBuf 對 write 操作進行了封裝,由 ByteBuf 的 write 操作負責進行剩余空間的校驗。
如果可用緩沖區不足,ByteBuf 會自動進行動態拓展。
ByteBuf 的功能性 API
順序讀操作(read)
讀取基本數據類型
Java 有 8 種基本數據類型分別是 boolean, byte, short, char, int, long, float, double。
Netty 的順序讀操作自然也少不了這些類型,
readByte():byte
readBoolean():boolean
readShort():short
readChar():char
readInt():int
readLong():long
readFloat():float
readDouble():double
從readIndex開始獲取24位整型值,readIndex增加3(注意:該類型並非 Java 的基本類型,大多數場景下用不到):
readMedium():int
除此以外,但是對於 byte, short, medium, int 還多出了四種無符號型
readUnsignedByte():byte
readUnsignedShort():int
readUnsignedMedium():int
readUnsignedInt():int
針對整形(short, medium, int, long)和浮點型(float, double), 還有小端模式 API:
readShortLE(): short
readUnsignedShortLE(): int
readMediumLE(): int
readUnsignedMediumLE(): int
readIntLE(): int
readUnsignedIntLE(): int
// 以下均形單影只,沒有 unsigned
readLongLE(): long
readFloatLE(): float
readDoubleLE(): double
關於大端小端,百度百科講得還挺好的:大小端模式,但是需要補充幾點:
- 大端模式是和我們的閱讀習慣一致的。
- JVM 默認采取的是大端模式編碼,即 Java 的客戶端/服務端之間收發信息,都按大端模式來處理。
讀取到目標 ? 中
然后就是 readBytes 方法將當前 ByteBuf 的數據讀取到目標?中,?的主要的參數類型有 ByteBuf, byte[], ByteBuffer, OutputStream, GatheringByteChannel, FileChannel。
這相當於從當前 ByteBuf 創建一個?類型的數據副本。
創建子區域(切片)
readSlice(length: int) :ByteBuf
- 返回當前 ByteBuf 新創建的子區域,子區域與原 ByteBuf 共享緩沖區,但是獨立維護自己的 readerIndex 和 writerIndex
- 新創建的子區域 readerIndex 為 0, writerIndex 為 length
- 如果讀取的長度 length 大於當前操作的 ByteBuf 的可寫字節數,將拋出 IndexOutOfBoundsException,操作失敗
順序寫操作(write)
寫入 Java 基本數據類型
寫入的時候,就沒有讀取時那么多“花里胡哨”的 unsigned 了,返回值清一色 ByteBuf,就是為了能夠 byteBuf.writeBoolean(true).writeInt(0).writeFloat(1.0f)
這樣鏈式調用。
writeBoolean(value : boolean): ByteBuf
writeByte(value: byte): ByteBuf
writeChar(value: char): ByteBuf
writeShort(value: short): ByteBuf
writeInt(value: int): ByteBuf
writeMedium(value: int): ByteBuf // 這個不是 Java 基本類型,算個特例
writeLong(value: long): ByteBuf
writeFloat(value: float): ByteBuf
writeDouble(value: double): ByteBuf
除了 boolean,byte,char,其他的還有小端模式 API
writeShortLE(value: short): ByteBuf
writeIntLE(value: int): ByteBuf
writeMediumLE(value: int): ByteBuf // 這個不是 Java 基本類型,算個特例
writeLongLE(value: long): ByteBuf
writeFloatLE(value: float): ByteBuf
writeDoubleLE(value: double): ByteBuf
將 ? 寫入當前 ByteBuf
- writeBytes 方法將 ? 類型(包含 ByteBuf, ByteBuffer)中的可讀字節寫入到當前 ByteBuf
- writeBytes 方法將 ? 類型(包含 byte[])中的所有字節寫入到當前 ByteBuf
- writeBytes 方法將 ? 類型(包含 InputStream,ScatteringByteChannel,FileChannel)中的所有內容寫入到當前 ByteBuf
內容填充 NUL(0x00)
writeZero(length: int): ByteBuf
- 將當前的緩沖區內容填充為 NUL(0x00),起始位置為 writerIndex,填充的長度為 length
- 填充成功之后 writerIndex += length
- 如果 length 大於當前 ByteBuf 的可寫字節數(
writableBytes():int
的返回值),會嘗試調用ensureWritable(int)
進行擴容。擴容失敗,拋出 IndexOutOfBoundsException 異常。
隨機讀寫(set 和 get)
除了順序讀寫之外,ByteBuf 還支持隨機讀寫,它與順序讀寫的最大差別在於可以隨意指定讀寫的索引位置。
無論是 get 還是 set 操作,ByteBuf 都會對齊索引和長度等進行合法性校驗,與順序讀寫一致。
但是,set 操作和 write 操作不同的是它不支持動態擴展緩沖區,所以使用者必須保證當前的緩沖區可寫的字節數大於需要寫入的字節長度,否則會拋出數據或者緩沖區越界異常。