Netty的ByteBuf API

ByteBuf 功能说明

上一篇文章 NIO入门之缓冲区Buffer 已经介绍了 Java 1.4 引入的 java.nio.Buffer
从功能角度而言,ByteBuffer 完全可以满足 NIO 编程的需要,但是由于 NIO 编程的复杂性,ByteBuffer 也有其局限性,它的主要缺点如下:

  1. ByteBuffer 长度固定,一旦分配完成,它的容量不能动态拓展和收缩,当需要编码的 POJO 对象大于 ByteBuffer 的容量时,会发生索引越界异常。
ByteBuffer buffer = ByteBuffer.allocate(1024); // 指定分配固定长度的容量
  1. ByteBuffer 只有一个标识位置的指针 position,读写的时候需要手动调用 flip() 和 rewind(),使用必须小心谨慎地处理这些 API,否则很容易导致程序处理失败;
buffer.clear();
socketChannel.read(buffer); // 读取前后都要选用合适的 NIO 的 API
buffer.flip();
  1. 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()
    获取当前的可读字节数

Netty的ByteBuf API
刚刚初始化的 ByteBuf 或者调用 clear 之后的 ByteBuf 读写指针都是停留在 0 的位置。
当前没有可读的字节,如果强行读取会抛出java.lang.IndexOutOfBoundsException:
Netty的ByteBuf API

  • readerIndex 和 writerIndex 之间的数据时可读取的,对应 java.nio.Buffer 的 position 和 limit 之间的数据。
  • writerIndex 和 capacity 之间的空间是可写的,等价于 Buffer limit 和 capacity 之间的可用空间。

Netty的ByteBuf API

特性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 异常。
上一篇:从 LengthFieldBasedFrameDecoder 看 netty 处理拆包


下一篇:pycharm 导入已有库 --librosa