ByteBuf是什么

网络数据的基本单位总是字节,JAVANIO提供了ByteBuffer(缓冲区)作为它的字节容器,ByteBuf则是Netty提供的ByteBuffer替代品,使用起来更灵活简单。
所有的网络通信都涉及字节序列的移动,所以高效易用的数据结构明显是必不可少的,Netty的ByteBuf实现满足并超越的这些需求。

ByteBuf的API

主要通过ByteBuf、ByteBufHolder两个组件暴露
ByteBuf接口的优点:

  • 可以被用户自定义的缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现了透明的零拷贝
  • 容量可以按需增长(类似于JDK的StringBuilder),这个在JDK的ByteBuffer是不允许容量动态增长的,因为JDK创建ByteBuffer时必须指定容量,而ByteBuf默认最大可扩容量是Integer的最大值,容量默认是256。
  • 在读和写两种模式之间切换不需要调用ByteBuffer的flip()方法
  • 读和写使用了不同的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化(池化是通过ByteBufHolder实现的)

    ByteBuf类的一些详细内容

    如何工作

    ByteBuf维护了两个不同的索引,一个用于读取,一个用于写入。当从ByteBuf读取时,它的readerIndex将会被递增已经被读取的字节数,同样如果是写入,它的writeIndex也会被递增。在JDK的只有一个position属性作为索引,读写需要切换模式。
    名称以read或者write开头的ByteBuf方法将会推进其对应的索引,而名称以set或get开头的操作则不会。可以指定ByteBuf的容量(默认容量是Integer.MAX_VALUE),当writeIndex索引超过容量则会抛出异常。每次调用write方法都会判断当前ByteBuf是否需要扩容。

    ByteBuf的使用模式

  1. 堆缓冲区(在ByteBuffer中叫非直接缓冲区的一致)

将数据存储在JVM的堆空间中是最常用的ByteBuf模式,这种模式被称为支撑数组,它在没有使用池化的情况下提供快速的分配和释放。
ByteBuf.hasArray();方法判断是否有一个支撑数组,false即直接缓冲区,反之。

  1. 直接缓冲区

为了避免每次调用本地IO操作之前后将缓冲区的内容复制到一个中间缓冲区(或者从中间缓冲区把内容复制到应用缓冲区),NIO在JDK中引入的ByteBuffer类允许JVM实现通过本地调用来分配内存,用于对象创建的内存分配不会永远来自于堆中。
直接缓冲区的使用:
直接缓冲区的内容驻留在常规的会被垃圾回收的堆之外,如果数据在堆上分配的缓冲区中,那么套接字发送它之前,JVM将会在内部把你的缓冲区复制到一个直接缓冲区中,如此便多了一个步骤,所以直接缓冲区是一种理想的选择。但是相对于堆的缓冲区,它们的分配和释放较为昂贵,如果正在处理遗留代码,因为数据不是在堆上,又不得不进行一次复制。一般事先知道数据将会被作为数据来访问,应该更愿意使用堆内存。

  1. 复合缓冲区(接近ByteBuffer中的分散读取和聚集写入)

复合缓冲区为ByteBuf提供了一个聚合视图,在这里可以根据需要添加或者删除ByteBuf实例,这是JDK的ByteBuffer缺失的一个特性。
Netty通过一个ByteBuf子类CompositeByteBuf实现,它提供将多个缓冲区表示为单个缓冲区的虚拟表示。该子类实例可能包含前面两种模式的ByteBuf实例,如果其中只有一个ByteBuf实例,那么对于该子类实例的hasArray()方法调用的将是该ByteBuf的值,否则它将返回false。

  1. /**
  2. * netty复合缓冲区的示例
  3. */
  4. public static void demo() {
  5. CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
  6. // 分配一个直接缓冲区
  7. ByteBuf headBuf = Unpooled.directBuffer();
  8. // 分配一个堆缓冲区
  9. ByteBuf footBuf = Unpooled.buffer();
  10. // 将建立两个缓冲区添加到复合缓冲区组件中
  11. messageBuf.addComponents(headBuf, footBuf);
  12. // 调用方法判断是否是支撑数组,==false
  13. System.out.println(messageBuf.hasArray());
  14. // 删除索引位置为0的缓冲区
  15. messageBuf.removeComponent(0);
  16. // 调用方法判断是否是支撑数组,==true,因为只有一个实例时直接调用footBuf的hasArray()
  17. System.out.println(messageBuf.hasArray());
  18. for (ByteBuf byteBuf : messageBuf) {
  19. System.out.println(byteBuf);
  20. }
  21. }
  1. @Override
  2. public boolean hasArray() {
  3. switch (componentCount) {
  4. case 0:
  5. return true;
  6. case 1:
  7. return components[0].buf.hasArray();
  8. default:
  9. return false;
  10. }
  11. }