Netty源码分析——ByteBuf
目录1、ByteBuffer介绍2、ByteBuffer缺点3、ByteBuf介绍4、ByteBuf常用API5、ByteBuf转换成标准的ByteBuffer6、ByteBuf源码分析7、最简单的将接收到的消息打印出来Netty 里面数据读写是以 ByteBuf 为单位进行交互的,ByteBuf是Byte数组的缓冲区,像java里的ByteBuffer1、By...
目录
Netty 里面数据读写是以 ByteBuf 为单位进行交互的,ByteBuf是Byte数组的缓冲区,像java里的ByteBuffer
1、ByteBuffer介绍
ByteBuffer只有一个指针position,读写的时候需要手动调用flip()和rewind()等,否则可能导致程序读写错误。
flip是把limit设为当前position,把position设为0,这样就可以从buffer开头,对该buffer进行读取了。一般在从Buffer读出数据前调用。
clear是把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。
rewind是把position设为0,limit不变,一般在把数据重写入Buffer前调用。
典型用法如下:
public class ByteBufferTest {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(88);
String value = "ByteBuffer学习";
buffer.put(value.getBytes());
System.out.println("position = " + buffer.position());
System.out.println("limit = " + buffer.limit());
System.out.println("remaining = " + buffer.remaining());
buffer.flip();
System.out.println("position = " + buffer.position());
System.out.println("limit = " + buffer.limit());
System.out.println("remaining = " + buffer.remaining());
byte[] dst = new byte[buffer.remaining()];
buffer.get(dst);
System.out.println(new String(dst));
}
}
输出:
position = 16
limit = 88
remaining = 72
position = 0
limit = 16
remaining = 16
ByteBuffer学习
可以看出调用flip操作前后的对比:
2、ByteBuffer缺点
- 长度固定,一旦分配完成,容量不能动态扩展收缩,当需要编码的对象大于ByteBuffer的容量时,就会发生索引越界异常
- 只有一个标识位置的指针position,读写的时候需要手动调用flip和rewind等
- api功能有限,不支持高级特性
3、ByteBuf介绍
ByteBuf有两个指针,读指针(readerIndex)、写指针(writerIndex),然后还有一个变量 capacity,表示 ByteBuf 底层内存的总容量。同时还有个maxCapacity,当bytebuf的capcity不足时的最大扩容容量。
刚开始readerIndex和writerIndex的位置都是0,随着数据的读取,readerIndex会增加,但是不会超过writerIndex,ByteBuf 里面总共有 writerIndex-readerIndex 个字节可读,由此可以推论出当 readerIndex 与 writerIndex 相等的时候,ByteBuf 不可读。
在读取之后,0~readerIndex之间的数据就是discard已丢弃的,调用discardReadBytes可以释放这部分空间。
写数据是从 writerIndex 指向的部分开始写,每写一个字节,writerIndex 自增1,直到增到 capacity,这个时候,表示 ByteBuf 已经不可写了,ByteBuf 里面其实还有一个参数 maxCapacity,当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错
两个指针把bytebuf分成了三部分,
- 第一个部分是已经丢弃的字节,这部分数据是无效的;
- 第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;
- 最后一部分的数据是可写字节,所有写到 ByteBuf 的数据都会写到这一段。最后一部分虚线表示的是该 ByteBuf 最多还能扩容多少容量
初始分配的bytebuf如①
写入N个字节后如②
读取M(< N)个字节后如③
调用discardReadBytes后如④
调用clear后恢复如①
4、ByteBuf常用API
- readableBytes() 与 isReadable()
readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,如果两者相等,则不可读,isReadable() 方法返回 false
- writableBytes()、 isWritable() 与 maxWritableBytes()
writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,如果两者相等,则表示不可写,isWritable() 返回 false,但是这个时候,并不代表不能往 ByteBuf 中写数据了, 如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex
- writeBytes(byte[] src) 与 buffer.readBytes(byte[] dst)
writeBytes() 表示把字节数组 src 里面的数据全部写到 ByteBuf,而 readBytes() 指的是把 ByteBuf 里面的数据全部读取到 dst
- writeByte(byte b) 与 buffer.readByte()
writeByte() 表示往 ByteBuf 中写一个字节,而 buffer.readByte() 表示从 ByteBuf 中读取一个字节
- slice和duplicate
slice() 方法从原始 ByteBuf 中截取一段,这段数据是从 readerIndex 到 writeIndex,同时,返回的新的 ByteBuf 的最大容量 maxCapacity 为原始 ByteBuf 的 readableBytes();duplicate() 方法把整个 ByteBuf 都截取出来,包括所有的数据,指针信息
slice() 方法与 duplicate() 方法的相同点是:底层内存以及引用计数与原始的 ByteBuf 共享,也就是说经过 slice() 或者 duplicate() 返回的 ByteBuf 调用 write 系列方法都会影响到 原始的 ByteBuf,但是它们都维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针.
5、ByteBuf转换成标准的ByteBuffer
由于通过NIO的SocketChannel进行网络读写时,操作的对象是jdk标准的ByteBuffer,所以看下ByteBuf如何转换成ByteBuffer
(1)nioBuffer():将当前的ByteBuf可读的缓冲区转换成ByteBuffer,对ByteBuffer的读写操作不会影响原ByteBuf的读写索引。
(2) nioBuffer(int index, int length):将当前的ByteBuf从index开始长度为length的缓冲区转换成ByteBuffer,对ByteBuffer的读写操作不会影响原ByteBuf的读写索引。
6、ByteBuf源码分析
堆内存字节缓冲区(HeapByteBuf):UnPoolHeapByteBuf、PooledHeapByteBuf
它的特点是内存的分配和回收都在堆,所以速度很快;缺点就是进行Socket的IO读写,需要把堆内存对应的缓冲区复制到内核Channel中,这内存复制会影响性能
直接内存缓冲区(DirectByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、PoolDirectByteBuf、PoolUnsafeDirectByteBuf它的特点是由于内存的分配在非堆(方法区),不需要内存复制,所以IO读取的速度较快,但是内存的分配较慢
当使用DirectByteBuf
可以实现零拷贝
总结:
根据两种内存的特点,我们可以知道,IO读写时最好使用DirectByteBuf,而在后端业务消息的解编码最好使用HeapByteBuf
7、最简单的将接收到的消息打印出来
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
//没用指定decoder和encoder,就用byteBuf来解析
ByteBuf in = (ByteBuf) msg;
try {
while (in.isReadable()) {
System.out.print((char) in.readByte());
}
} finally {
((ByteBuf) msg).release();
}
}
更多推荐
所有评论(0)