前言
在网络上传输的数据总是以字节为单位,Java NIO使用ByteBuffer作为字节的容器,使用起来比较繁琐与复杂。Netty使用ByteBuf作为ByteBuffer的替代品,但又具备ByteBuffer的功能且功能更强大。
ByteBuf
ByteBuf将会是使用Netty框架开发网络程序中使用频率较高的工具之一;Netty的数据处理通常由ByteBuf与ByteBufHolder这两个组件完成。下面先来看看ByteBuf的一些优点:
- 通过内置的复合缓冲区类型实现了透明的零拷贝
- 容量可以按需要增长
- 在读与写两种模式之间切换不需要像ByteBuffer一样需要调用flip方法,读与写模式之间使用不同的索引
- 支持方法之间的链式调用
- 用户可以自定义扩展缓冲区类型
- 支持池化
- 支持引用计数
ByteBuf维护了两个不同的索引,一个是读索引(readIndex)一个是写索引(writeIndex),调用read开头的方法时readIndex将会增加,调用write开头方法时writeIndex将会增加。

这是一个初始大小为16bit的ByteBuf中writeIndex、readIndex、capacity的使用情况,在ByteBuf中这三个变量的关系是0<=readIndex<=writeIndex<=capacity。

我们考虑下,如果readIndex==writeIndex时继续读取数据会出现什么情况?如果继续读将会读取到写入的数据,所以writeIndex-readIndex就相当时最大可读的字节数,在ByteBuf中readIndex ==writeIndex时再继续读就会出现IndexOutOfBoundsException异常,另外没有在图中体现的还有一个变量maxCapacity,代表ByteBuf的最大容量,当写操作超过这个值时将会提示一个异常,默认的限制是Interge.MAX_VALUE。在图中还有一个变量capacity,这个表示当前容量。
堆缓冲区、直接缓冲区与复合缓存冲区
ByteBuf分为两个缓存区模式:堆缓冲区与直接缓冲区;堆缓冲区中将缓冲的数据存放在JVM堆内存中,这种模式也被称为支持数组,内部使用byte[]存储数据,对于缓冲区的扩容使用System.arrayCopy操作,这个模式的优点在于可以在没有使用池化技术的情况下快速分配和释放内存,下面通过示例来看看如何使用:
1 | //分配一个非池化的buffer |
如果是直接缓冲区在尝试调用buf.array()方法获取缓冲数组时会得到一个UnsuppertedOperationException的异常。
在某些情况下使用堆缓冲区会导致额外的拷贝操作,这进可以使用堆外内存来避免堆外数据拷贝到堆内的情况;为了应对这样的需求可以使用ByteBuf的直接缓冲区模式。Java JDK中的Bytebuffer类允许JVM实现通过本地调用来分配内存,在ByteBuffer的Javadoc明确指出:“直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外”。这也是为会直接缓冲区对于网络数据传输是理想的选择;但直接缓冲区也有它自己的缺点,在分配与释放上是昂贵的。下面通过一个示例来看看如何使用直接缓冲区读取数据:
1 | //创建一个直接内存缓冲区 |
如果有多个缓冲区,这些缓冲区中混合着直接缓冲与堆缓冲,现在需要读取这些缓冲区的数据,通常我们会创建一个数组后再将这些缓冲区的数据一一复制到这个数组中;但这无疑会增加数据拷贝带来的损耗;Netty为应对这样的场景提供了复合缓冲区;复合缓冲区可以将多个不同模式的ByteBuf缓冲区进行聚合,这个聚合的类便是CompositeByteBuf,有了CompositeByteBuf就可以非常容易的对缓冲区进行聚合后继续操作,且可以防止因聚合带来的数据拷贝问题,还是通过一段示例来看看如何使用:
1 | //不使用CompositeByteBuf进行缓冲区的聚合 |
ByteBuf 常用方法示例
ByteBuf常用方法:
1 | ByteBuf buffer = Unpooled.buffer(); |