Netty数据容器ByteBuf详解

概述

网络数据的基本单位总是字节。Java NIO 提供了ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。

Netty 的ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。

优点

读写指针分离,不需要像 ByteBuffer 一样切换读写模式;
很多地方体现零拷贝理念,例如 slice、duplicate、CompositeByteBuf,减少了数据拷贝过程,提升了性能
支持方法的链式调用;
支持引用计数;
支持池化;
自动扩容;

原理

NIO的ByteBuffer对象只有一个指针,进行读写时,需要切换模式,而ByteBuf设计上就采用了两个索引指针,即读指针与写指针,提高了灵活性和便捷性。

池化/非池化

默认情况下,采用的是池化方式来提升性能。

如需修改该设置,需通过参数来控制

-Dio.netty.allocator.type={unpooled|pooled}

注:非池化,也可以直接使用工具类Unpooled的静态方法来创建缓冲区对象。

创建对象

ByteBuf是一个数据容器,底层实现有两种方式,一是在jvm的堆上分配内存,称之为堆内存;另外一种是通过调用本地方法分配服务器的物理内存,称之为直接内存。

创建对象可以通过ByteBufAllocator类的不同方法来指定以什么样的方式来分配内存。

//以下两句等同,buffer方法内部实际调用了heapBuffer方法,都是分配堆内存
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer()
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer()
//分配直接内存
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer();

堆内存模式,底层实际是由数组来实现,接受GC的管理。

当需要进行网络传输时,使用直接内存模式,将会少一次从堆空间拷贝到发送缓冲区的过程,因此读写性能更高。但直接内存的创建和销毁的代价昂贵,因此通常采用池化策略来提升性能。
直接内存不受 JVM 垃圾回收的管理,因此要注意及时主动释放。

ByteBuf对象内置了自动扩容功能,无需人工干预。

读取操作

有两组API,一组是读取数据,同时将读指针后移,方法命名采用 read+类型 模式
在这里插入图片描述

另外一组是只读取数据,读指针不变,方法命名采用 get+类型 模式
在这里插入图片描述

写入操作

写入操作与读取操作类似,也是两组API,一组写入数据的同时,写指针后移,实际是在末尾追加数据,另外一组只写数据,写指针不变,实际是更新现有的数据。

在这里插入图片描述

在这里插入图片描述

销毁对象

前面说了,内存分配有堆内内存和直接内存两种模式,并且还使用了池化机制,内存的回收机制相对复杂一些。

Netty 采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口,规则如下:

  • 每个 ByteBuf 对象的初始计数为 1
  • 调用 release 方法计数减 1
  • 调用 retain 方法计数加 1
  • 当计数为 0 时,底层内存会被回收

        //直接调用对象的方法
        buffer.retain();
        buffer.release();
        //通过工具类来调用
        ReferenceCountUtil.retain(buffer);
        ReferenceCountUtil.release(buffer);

辅助功能

缓冲区工具类ByteBufUtil

方法prettyHexDump可以友好地显示缓冲区内容,对于开发调试非常有用

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64                                     |abcd            |
+--------+-------------------------------------------------+----------------+ 

缓冲区容器接口ByteBufHolder

这是一个缓冲区对象的容器,实际是一个接口,在实际应用中,不仅需要缓冲区来保存对象,还需要一些额外的属性,例如HTTP 响应,除了表示为字节的内容,还包括状态码、cookie 等,这时候就可以自己实现一个类,实现ByteBufHolder接口,扩展属性来使用。

以下是netty自己实现的类,处理http请求的

/**
 * Default implementation of {@link FullHttpRequest}.
 */
public class DefaultFullHttpRequest extends DefaultHttpRequest implements FullHttpRequest {
    private final ByteBuf content;
    private final HttpHeaders trailingHeader;

非池化缓冲区工具Unpooled

提供了静态的辅助方法来创建未池化的ByteBuf实例,使得ByteBuf 同样可用于那些并不需要Netty 的其他组件的非网络项目,使得其能得益于高性能的可扩展的缓冲区API。

在这里插入图片描述

不常用操作

清空对象

clear方法,注意,这里的清空,只是重置读写指针的位置,数据并没有擦除,但是对于使用者来说,相当于一个空白的全新缓冲区了。

拷贝操作

主要是两类,浅拷贝duplicate,共用存储,读写指针独立;深度拷贝copy,拷贝数据,完全独立的新对象。

重复读写

某些情况下,希望重复读或者写数据,则可以调用一个mark方法,标记读/写指针位置,然后读/写数据后,再调用reset方法重置读写指针位置。

        buffer.markReaderIndex();
        buffer.readByte();
        buffer.readDouble();
        buffer.resetReaderIndex();

切片操作

从一个ByteBuf对象中,截取部分形成一个新对象,新对象的读写指针是独立的,但与原对象共用底层存储。

      //无参代表截取读写指针之间的数据
      buffer.slice();

      //参数分别为起始位置与长度
      buffer.slice(0,2);

组合对象

ByteBuf类还有一个子类,名字叫CompositeByteBuf,其目的是实现多个ByteBuf对象的聚合视图,即可以向这个CompositeByteBuf对象中添加多个ByteBuf的对象,既可以是堆内存模式,也可以是直接内存模式,还可以是混合模式。实际的使用场景较少,如发送Http请求时,如多个请求的body部分相同,header不同,则可以采用组合模式,公用body,减少数据创建和拷贝来提升性能。

        //创建组合缓冲区
        CompositeByteBuf compositeByteBuf=ByteBufAllocator.DEFAULT.compositeBuffer();
        //添加对象1
        compositeByteBuf.addComponent(buffer1);
        //添加对象2
        compositeByteBuf.addComponent(buffer2);

        //移除索引0处的对象
        compositeByteBuf.removeComponent(0);

抛弃已读数据

使用discardReadBytes方法,可以将已读数据抛弃,即将未读数据拷贝到索引0的位置,同时移动读指针到开头,写指针到结尾。

注意,这个过程会发生数据拷贝,而不是零复制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学海无涯,行者无疆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值