Netty之ByteBuf相关

概述

网络数据的基本单位总是字节。Java NIO 提供了ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些烦琐。
Netty 的ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。

优点

  1. 它可以被用户自定义的缓冲区类型扩展
  2. 通过内置的复合缓冲区类型实现了透明的零拷贝;
  3. 容量可以按需增长;
  4. 在读和写这两种模式之间切换不需要调用额外flip()的方法;
  5. 读和写使用了不同的索引
  6. 支持方法的链式调用;
  7. 支持引用计数;
  8. 支持池化。

ByteBuf类

在这里插入图片描述
ByteBuf是一个抽象类。

ReferenceCounted:

  • 对象的初始引用计数为 1 。
  • 当引用计数器值为 0 时,表示该对象不能再被继续引用,只能被释放。

工作方式

  • readerIndex ,读索引。
  • writerIndex ,写索引。
  • capacity ,当前容量。
  • maxCapacity ,最大容量。
    在这里插入图片描述
    当 writerIndex 写入超过 capacity 时,可自动扩容。每次扩容的大小,为 capacity 的 2 倍。当然,前提是不能超过 maxCapacity 大小。

ByteBuf 通过 readerIndex 和 writerIndex 两个索引,解决 ByteBuffer 的读写模式的问题。

使用模式

  • 堆缓冲区
    最常用的ByteBuf 模式是将数据存储在JVM 的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。可以由hasArray()来判断检查ByteBuf 是否由数组支撑。适合有遗留数据需要处理的情况。
    在这里插入图片描述
  • 直接缓冲区
    直接缓冲区避免了I/O前后将缓冲区的内容复制到一个中间缓冲区,适用于网络数据传输。它的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。而且不适合用来处理遗留代码:
    在这里插入图片描述
  • 复合缓冲区
    它为ByteBuf提供了一个聚合视图,可以根据你的需要添加或删除ByteBuf实例。
    通过ByteBuf子类——CompositeByteBuf来实现这个模式,它将多个缓冲区合并为单个来虚拟表示。
    使用方式:
    在这里插入图片描述

字节级操作

随机访问索引

如同在普通的Java 字节数组中一样,ByteBuf 的索引是从零开始的:第一个字节的索引是0,最后一个字节的索引总是capacity() - 1。
在这里插入图片描述
这种使用一个索引 i 来访问的操作就随机访问索引。

顺序访问索引

利用 ByteBuf 里内置的索引进行访问。
在这里插入图片描述

可丢弃字节

可丢弃字节的分段包含了已经被读过的字节。可以调用discardReadBytes()方法丢弃它们。
在这里插入图片描述
可读字节的内容不会改变,它是被移动的。而writerIndex后的内容(可写字节)就无所谓了,因为反正是要被写的,会被覆盖。

因可读字节的移动而导致的内存复制会耗费时间,所以此操作应当少做。只有在最需要(内存非常宝贵)的时候做。

可读字节

新分配、包装、复制后的缓冲区readerIndex值为0。
read* 和 skip* 操作会使 readerIndex 加 1。
在这里插入图片描述
注意:如果方法以ByteBuf为参数且作为写入目标,但没有指定目标索引参数,那么目标缓冲区的 writerIndex 也会增加,例如:readBytes(ByteBuf dest);

可写字节

writerIndex 的默认值为0。
write* 会从当前 writerIndex 处写数据,并增加 writerIndex 的大小。
在这里插入图片描述
注意:如果写操作的目标也是 ByteBuf,但没有指定源索引参数,那么源缓冲区的 readerIndex 也会增加,例如:writeBytes(ByteBuf dest);

索引管理

调用 markReaderIndex()、markWriterIndex()、resetWriterIndex() 和 resetReaderIndex() 来标记和重置 ByteBuf 的 readerIndex 和 writerIndex。

也可以通过调用readerIndex(int)或者writerIndex(int)来将索引移动到指定位置。试图将任何一个索引设置到一个无效的位置都将导致一个IndexOutOfBoundsException。

可以通过调用clear()方法来将readerIndex 和writerIndex 都设置为0。注意,这并不会清除内存中的内容。

查找

在ByteBuf中有多种可以用来确定指定值的索引的方法。最简单的是使用indexOf()方法。
较复杂的查找可以通过调用forEachByte方法。
下面代码展示了一个查找回车符(\r)的例子。
在这里插入图片描述

派生缓冲区

派生缓冲区为ByteBuf 提供了以专门的方式来呈现其内容的视图。这类视图是通过以下方法被创建的:

  • duplicate();
  • slice();
  • slice(int, int);
  • Unpooled.unmodifiableBuffer(…);
  • order(ByteOrder);
  • readSlice(int)。
    每个这些方法都将返回一个新的ByteBuf 实例,它具有自己的读索引、写索引和标记索引。其内部存储和JDK 的ByteBuffer 一样也是共享的。这意味着,如果你修改了新的 ByteBuf 实例,源实例也会被修改。

ByteBuf 复制:如果需要一个现有缓冲区的真实副本,请使用copy()或者copy(int, int)方法。不同于派生缓冲区,由这个调用所返回的ByteBuf 拥有独立的数据副本。

读/写

有两类读/写操作:

  • get* 和 set* ,从给定的索引开始,并且保持索引不变
  • read* 和 write* ,从给定的索引开始,并且会根据已经访问过的字节数对索引进行调整。

get*

在这里插入图片描述

set*

在这里插入图片描述
示例:
在这里插入图片描述

read*

在这里插入图片描述

write*

在这里插入图片描述
示例:
在这里插入图片描述

更多

在这里插入图片描述

ByteBuf分配

按需分配:ByteBufAllocator 接口

为了降低分配和释放内存的开销,Netty 通过 ByteBufAllocator 接口实现了(ByteBuf的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf实例。
在这里插入图片描述

可以通过Channel(每个都可以有一个不同的 ByteBufAllocator 实例)或者绑定到ChannelHandler 的ChannelHandlerContext 获取一个到 ByteBufAllocator 的引用。(channel.alloc()或ctx.alloc())

Netty提供了两种 ByteBufAllocator 的实现:PooledByteBufAllocator 和 UnpooledByteBufAllocator。前者池化了 ByteBuf 的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例。
Netty4默认使用了PooledByteBufAllocator。

Unpooled 缓冲区

Netty 提供了一个简单的称为Unpooled 的工具类,它提供了静态的辅助方法来创建未池化的ByteBuf实例。
在这里插入图片描述Unpooled 类还使得ByteBuf 同样可用于那些并不需要 Netty 的其他组件的非网络项目。

ByteBufUtil类

ByteBufUtil 提供了用于操作ByteBuf 的静态的辅助方法。因为这个API 是通用的,并且和池化无关,所以这些方法已然在分配类的外部实现。

这些静态方法中最有价值的可能就是hexdump()方法,它以十六进制的表示形式打印ByteBuf 的内容。这在各种情况下都很有用,例如,出于调试的目的记录ByteBuf 的内容。十六进制的表示通常会提供一个比字节值的直接表示形式更加有用的日志条目,此外,十六进制的版本还可以很容易地转换回实际的字节表示。

另一个有用的方法是boolean equals(ByteBuf, ByteBuf),它被用来判断两个ByteBuf实例的相等性。

ByteBufHolder 接口

用于存储各种属性值。如,状态码,cookie等。
在这里插入图片描述

引用计数

引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。Netty 在第4 版中为ByteBuf 和 ByteBufHolder 引入了引用计数技术,它们都实现了 ReferenceCounted 接口。

引用计数对于池化实现来说至关重要,它降低了内存分配的开销。
示例:
引用计数:
在这里插入图片描述
释放引用计数的对象:
在这里插入图片描述
参考自:《Netty实战》

©️2020 CSDN 皮肤主题: 岁月 设计师: pinMode 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值