netty缓冲区ByteBuf Netty提供了一个缓冲类ByteBuf来操作字节进行数据传输,ByteBuf可以看做是一个数据容器,其提供了两个索引,一个用来读,一个用来写
NIO中ByteBuffer的缺点 其实在java原生NIO中是有缓冲区ByteBuffer的,为什么netty还要再写一套呢?是因为原生的ByteBuffer有很多缺点
ByteBuffer长度固定,一旦创建完成,容量就不能改变,不支持动态扩展和收缩。如果我们编码的对象大于其容量时,会出现索引越界异常
ByteBuffer只有一个标识位置的指针position,读写时需要手动调用flip()和rewind()来进行切换,使用不方便
netty如何解决?
长度固定问题: netty实现动态扩展,如果剩余空间不足,会进行扩容,直到指定的最大容量maxCapacity(小于4M前,从64 byte开始每次扩一倍,大于4M后,每次扩4M)
读写切换问题:netty提供了两个位置指针分别操作读写,读操作使用readerIndex,写操作使用writerIndex
随机访问索引 就像普通的字节数组一样,ByteBuf使用zero-based indexing,这意味着第一个字节的索引总是0,最后一个字节的索引是capacity - 1,例如,要迭代缓冲区中的所有字节,可以使用如下方式
1 2 3 4 for (int i = 0 ; i < buffer.capacity(); i ++) { byte b = buffer.getByte(i); System.out.println((char ) b); }
顺序访问索引 1 2 3 4 5 * {@link ByteBuf} provides two pointer variables to support sequential * read and write operations - {@link * operation and {@link * respectively. The following diagram shows how a buffer is segmented into * three areas by the two pointers:
ByteBuf提供两个指针变量来支持顺序读写操作,readerIndex作为读指针,writerIndex作为写指针。0到readerIndex之间为已经读过的缓冲区,可以调用discardReadBytes来重用这部分空间,节约内存;readerIndex到writerIndex之间的空间为可读的字节缓冲区;writerIndex到capacity之间为可写的字节缓冲区
1 2 3 4 5 6 * +-------------------+------------------+------------------+ * | discardable bytes | readable bytes | writable bytes | * | | (CONTENT) | | * +-------------------+------------------+------------------+ * | | | | * 0 <= readerIndex <= writerIndex <= capacity
这个就是为什么ByteBuf不需要使用flip()方法来切换读和写模式的原因,而JDK中的ByteBuffer是只有一个方法来设置索引的
可读字节readable bytes 可读字节存储的是实际的数据,调用read..()或者skip..()方法会使得readerIndex增加
读取所有字节
1 2 3 while (buffer.isReadable()) { System.out.println(buffer.readByte()); }
可写字节writable bytes 可写字节是需要被填充的空间
填充随机整数
1 2 3 while (buffer.maxWritableBytes() >= 4 ) { buffer.writeInt(random.nextInt()); }
可丢弃字节discardable bytes 可丢弃的字节说明已经被读过了,可以使用discardReadBytes()来回收空间
调用前
1 2 3 4 5 6 7 * BEFORE discardReadBytes()* * +-------------------+------------------+------------------+* | discardable bytes | readable bytes | writable bytes |* +-------------------+------------------+------------------+* | | | |* 0 <= readerIndex <= writerIndex <= capacity
调用后
可以看到调用后由于空间被回收可用空间被增大
清除索引 可以通过调用clear()方法来设置readerIndex和writerIndex为0,该操作不会清除缓存数据,仅仅是清除了两个指针
调用前
调用后
使用模式 netty创建缓冲区可以创建堆缓冲区、堆外缓冲区、复合缓冲区
1 2 3 4 5 6 7 ByteBuf byteBuf = Unpooled.buffer(8 ); ByteBuf directBuffer = Unpooled.directBuffer(8 ); CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); compositeByteBuf.addComponents(byteBuf,directBuffer);
对于后端业务消息的编解码使用堆缓冲区,而在IO通信线程的读写缓冲区使用堆外缓冲区,组合使得性能最优
堆缓冲区 ByteBuf将数据存储在JVM的堆空间,通过将数据存储在数组的实现,优点是可以快速分配,当不使用时可以被JVM自动回收;缺点是如果进行Socket的IO读写,需要额外进行一次内存复制,将堆内存对应的缓冲区复制到内核Channel中,性能会有一定下降
直接缓冲区(堆外缓冲区) 直接缓冲区中的内存不是使用的堆内存,其目的是
通过免去中间交换的内存拷贝,提升IO处理速度
直接缓冲区的内容可以驻留在垃圾回收扫描的堆区外
其大小的限制是通过 -XX:MaxDirectMemorySize
来限制的
其缺点是在内存空间的分配和释放会比在堆缓冲区更复杂
复合缓冲区CompositeByteBuf 复合缓冲区可以创建多个不同的ByteBuf,然后提供一个这些ByteBuf的视图 ,可以动态的添加和删除其中的ByteBuf,由于复合缓冲区是一个视图,所以其hasArray方法总是返回false