网络传输的基本单位总是字节,JDK使用ByteBuffer作为Nio网络编程的数据容器,但是这个类使用过于复杂,存在一些缺点,例如:它不支持扩容、读写模式切换需要经常调用flip()
,导致开发者经常因为忘记调用而导致无法读取写入的数据。
Netty使用ByteBuf来代替JDK的ByteBuffer,它有以下优点:
flip()
。
1. 工作模式
ByteBuf分别维护读写索引readerIndex和writerIndex,写数据时writerIndex不断递增,读数据时readerIndex不断递增,readerIndex达到writerIndex代表无数据可读,writerIndex达到capacity代表不可写。
通过这种方式,ByteBuf将缓冲区的数据分成了三段,分别是:
数据段 | 范围 |
---|---|
可丢弃字节 | 0~readerIndex |
可读字节 | readerIndex~writerIndex |
可写字节 | writerIndex~capacity |
如何回收这部分「可丢弃字节」呢?ByteBuf提供了discardReadBytes()
方法,它会移动readerIndex和writerIndex,同时将「可读字节」的数据向前复制。由于会导致内存复制,因此不建议频繁调用此方法。
ByteBuf还提供了clear()
方法用来清空缓冲区,它仅仅重置索引,不会有任何的内存复制,因此它速度极快。
1.1 顺序读写和随机读写
ByteBuf支持顺序读写和随机读写,顺序读写会移动读写索引,随机读写不会。
顺序读写的方法名以read和write开头,随机读写的方法名以get和set开头。
除了可以往ByteBuf写入基本的字节数组外,还可以写入Java八大基本数据类型,Netty自己会完成字节的转换。
例如,往HeapByteBuf写入一个int,源码如下:
static void setInt(byte[] memory, int index, int value) {
memory[index] = (byte) (value >>> 24);
memory[index + 1] = (byte) (value >>> 16);
memory[index + 2] = (byte) (value >>> 8);
memory[index + 3] = (byte) value;
}
往ByteBuf写数据的API:
方法 | 说明 |
---|---|
writeBytes() | 写入字节数组 |
writeByte() | 写入一个字节 |
writeShort() | 写入一个short,2字节 |
writeInt() | 写入一个int,4字节 |
writeLong() | 写入一个long,8字节 |
writeFloat() | 写入一个float,4字节 |
writeDouble() | 写入一个double,8字节 |
writeChar() | 写入一个char,2字节,高位被忽略 |
writeBoolean() | 写入一个boolean,1字节 |
从ByteBuf中读数据API同上,把write改为read即可。
以上两种是顺序读写,会移动读写索引,写入时如果空间不够,ByteBuf还会自动扩容,下面再说说随机读写。
ByteBuf底层还是数组,一块连续的内存空间,可以根据索引快速定位,支持快速随机读写。随机读写方法以get和set开头,不会移动读写索引,如果index越界不会扩容,只会抛异常。
如下是从HeapByteBuf中随机读取一个int值的源码:
@Override
public int getInt(int index) {
// 检查index是否合理,有没有超出容量
checkIndex(index, 4);
return _getInt(index);
}
@Override
protected int _getInt(int index) {
return HeapByteBufUtil.getInt(array, index);
}
static int getInt(byte[] memory, int index) {
return (memory[index] & 0xff)