Netty In Action中文版 - 第五章:Buffers(缓冲)
本章介绍
- ByteBuf
- ByteBufHolder
- ByteBufAllocator
- 使用这些接口分配缓冲和执行操作
每当你需要传输数据时,它必须包含一个缓冲区。Java NIO API自带的缓冲区类是相当有限的,没有经过优化,使用JDK的ByteBuffer操作更复杂。缓冲区是一个重要的组建,它是API的一部分。Netty提供了一个强大的缓冲区实现用于表示一个字节序列,并帮助你操作原始字节或自定义的POJO。Netty的ByteBuf相当于JDK的ByteBuffer,ByteBuf的作用是在Netty中通过Channel传输数据。它被重新设计以解决JDK的ByteBuffer中的一些问题,从而使开发人员开发网络应用程序显得更有效率。本章将讲述Netty中的缓冲区,并了解它为什么比JDK自带的缓冲区实现更优秀,还会深入了解在Netty中使用ByteBuf访问数据以及如何使用它。
5.1 Buffer API
- ByteBuf
- ByteBufHolder
Netty使用reference-counting(引用计数)的时候知道安全释放Buf和其他资源,虽然知道Netty有效的使用引用计数,这都是自动完成的。这允许Netty使用池和其他技巧来加快速度和保持内存利用率在正常水平,你不需要做任何事情来实现这一点,但是在开发Netty应用程序时,你应该处理数据尽快释放池资源。
- 可以自定义缓冲类型
- 通过一个内置的复合缓冲类型实现零拷贝
- 扩展性好,比如StringBuffer
- 不需要调用flip()来切换读/写模式
- 读取和写入索引分开
- 方法链
- 引用计数
- Pooling(池)
5.2 ByteBuf - 字节数据容器
5.2.1 ByteBuf如何在工作?
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
5.2.2 不同类型的ByteBuf
- ByteBuf directBuf = Unpooled.directBuffer(16);
- if(!directBuf.hasArray()){
- int len = directBuf.readableBytes();
- byte[] arr = new byte[len];
- directBuf.getBytes(0, arr);
- }
访问直接缓冲区的数据数组需要更多的编码和更复杂的操作,建议若需要在数组访问数据使用堆缓冲区会更好。
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
- CompositeByteBuf compBuf = Unpooled.compositeBuffer();
- ByteBuf heapBuf = Unpooled.buffer(8);
- ByteBuf directBuf = Unpooled.directBuffer(16);
- //添加ByteBuf到CompositeByteBuf
- compBuf.addComponents(heapBuf,directBuf);
- //删除第一个ByteBuf
- compBuf.removeComponent(0);
- Iterator<ByteBuf> iter = compBuf.iterator();
- while(iter.hasNext()){
- System.out.println(iter.next().toString());
- }
- //使用数组访问数据
- if(!compBuf.hasArray()){
- int len = compBuf.readableBytes();
- byte[] arr = new byte[len];
- compBuf.getBytes(0, arr);
- }
CompositeByteBuf是ByteBuf的子类,我们可以像操作BytBuf一样操作CompositeByteBuf。并且Netty优化套接字读写的操作是尽可能的使用CompositeByteBuf来做的,使用CompositeByteBuf不会操作内存泄露问题。
5.3 ByteBuf的字节操作
5.3.1 随机访问索引
ByteBuf使用zero-based-indexing(从0开始的索引),第一个字节的索引是0,最后一个字节的索引是ByteBuf的capacity - 1,下面代码是遍历ByteBuf的所有字节:
- //create a ByteBuf of capacity is 16
- ByteBuf buf = Unpooled.buffer(16);
- //write data to buf
- for(int i=0;i<16;i++){
- buf.writeByte(i+1);
- }
- //read data from buf
- for(int i=0;i<buf.capacity();i++){
- System.out.println(buf.getByte(i));
- }
注意通过索引访问时不会推进读索引和写索引,我们可以通过ByteBuf的readerIndex()或writerIndex()来分别推进读索引或写索引。
5.3.2 顺序访问索引
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
5.3.3 Discardable bytes废弃字节
我们可以调用ByteBuf.discardReadBytes()来回收已经读取过的字节,discardReadBytes()将丢弃从索引0到readerIndex之间的字节。调用discardReadBytes()方法后会变成如下图:
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
5.3.4 可读字节(实际内容)
- ByteBuf buf = Unpooled.buffer(16);
- while(buf.isReadable()){
- System.out.println(buf.readByte());
- }
(代码于原书中有出入,原书可能是基于Netty4之前的版本讲解的,此处基于Netty4)
5.3.5 可写字节Writable bytes
任何写的操作会增加writerIndex。若写操作的参数也是一个ByteBuf并且没有指定数据源索引,那么指定缓冲区的readerIndex也会一起增加。若没有足够的可写字节会抛出IndexOutOfBoundException。新分配的缓冲区writerIndex的默认值是0。下面代码显示了随机一个int数字来填充缓冲区,直到缓冲区空间耗尽:
- Random random = new Random();
- ByteBuf buf = Unpooled.buffer(16);
- while(buf.writableBytes() >= 4){
- buf.writeInt(random.nextInt());
- }
5.3.6 清除缓冲区索引Clearing the buffer indexs
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
下图显示了调用clear()之后:
![](https://ucc.alicdn.com/notfound.png?x-oss-process=image/resize,w_1400/format,webp)
5.3.7 搜索操作Search operations
5.3.8 标准和重置Mark and reset
5.3.9 衍生的缓冲区Derived buffers
- // get a Charset of UTF-8
- Charset utf8 = Charset.forName("UTF-8");
- // get a ByteBuf
- ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
- // slice
- ByteBuf sliced = buf.slice(0, 14);
- // copy
- ByteBuf copy = buf.copy(0, 14);
- // print "Netty in Action rocks!"
- System.out.println(buf.toString(utf8));
- // print "Netty in Act"
- System.out.println(sliced.toString(utf8));
- // print "Netty in Act"
- System.out.println(copy.toString(utf8));
5.3.10 读/写操作以及其他一些操作
- get/set操作以索引为基础,在给定的索引设置或获取字节
- 从当前索引开始读写,递增当前的写索引或读索引
ByteBuf的各种读写方法或其他一些检查方法可以看ByteBuf的源码,这里不赘述了。
5.4 ByteBufHolder
ByteBufHolder是一个辅助类,是一个接口,其实现类是DefaultByteBufHolder,还有一些实现了ByteBufHolder接口的其他接口类。ByteBufHolder的作用就是帮助更方便的访问ByteBuf中的数据,当缓冲区没用了后,可以使用这个辅助类释放资源。ByteBufHolder很简单,提供的可供访问的方法也很少。如果你想实现一个“消息对象”有效负载存储在ByteBuf,使用ByteBufHolder是一个好主意。
5.4.1 ByteBufAllocator
- ServerBootstrap b = new ServerBootstrap();
- b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- // get ByteBufAllocator instance by Channel.alloc()
- ByteBufAllocator alloc0 = ch.alloc();
- ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- //get ByteBufAllocator instance by ChannelHandlerContext.alloc()
- ByteBufAllocator alloc1 = ctx.alloc();
- ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);
- }
- });
- }
- });
Netty有两种不同的ByteBufAllocator实现,一个实现ByteBuf实例池将分配和回收成本以及内存使用降到最低;另一种实现是每次使用都创建一个新的ByteBuf实例。Netty默认使用PooledByteBufAllocator,我们可以通过ChannelConfig或通过引导设置一个不同的实现来改变。更多细节在后面讲述。
5.4.2 Unpooled
- //创建复合缓冲区
- CompositeByteBuf compBuf = Unpooled.compositeBuffer();
- //创建堆缓冲区
- ByteBuf heapBuf = Unpooled.buffer(8);
- //创建直接缓冲区
- ByteBuf directBuf = Unpooled.directBuffer(16);