每当你需要传输数据时,它必须包含一个缓冲区。Java NIO API 自带的缓冲区类是相当有限的,没有经过优化,使用 JDK 的
ByteBuffer 操作更复杂。缓冲区是一个重要的组建,它是 API
的一部分。Netty提供了一个强大的缓冲区实现用于表示一个字节序列,并帮助你操作原始字节或自定义的 POJO。Netty 的 ByteBuf
相当于 JDK 的ByteBuffer,ByteBuf的作用是在 Netty 中通过 Channel 传输数据。它被重新设计以解决 JDK
的 ByteBuffer 中的一些问题, 从而使开发人员开发网络应用程序显得更有效率。

Netty 的缓冲 API 有两个接口

一: ByteBuf
当需要与远程进行交互时,需要以字节码发送/接收数据。由于各种原因,一个高效、方便、易用的数据接口是必须的,而 Netty 的 ByteBuf 满足这些需求,ByteBuf 是一个很好的经过优化的数据容器, 我们可以将字节数据有效的添加到 ByteBuf 中或从 ByteBuf 中获取数据。ByteBuf 有 2 部分:一个用于读,一个用于写。我们可以按顺序的读取数据,并且可以跳到开始重新读一遍。 所有的数据操作, 我们只需要做的是调整读取数据索引和再次开始读操作。
写入数据到 ByteBuf 后, 写入索引是增加的字节数量。 开始读字节后, 读取索引增加。你可以读取字节,直到写入索引和读取索引处理相同的位置,次数若继续读取,则会抛出IndexOutOfBoundsException。调用 ByteBuf 的任何方法开始读/写都会单独维护读索引和写索引。ByteBuf 的默认最大容量限制是 Integer.MAX_VALUE,写入时若超出这个值将会导致一个异常。
ByteBuf 类似于一个字节数组,最大的区别是读和写的索引可以用来控制对缓冲区数据的访问。
使用 Netty 时会遇到 3 种不同类型的 ByteBuf:

  • Heap Buffer( 堆缓冲区)
    最常用的类型是 ByteBuf 将数据存储在 JVM 的堆空间,这是通过将数据存储在数组的实现。堆缓冲区可以快速分配,当不使用时也可以快速释放。它还提供了直接访问数组的方法,通过ByteBuf.array()来获取 byte[]数据。 访问非堆缓冲区 ByteBuf 的数组会导致 UnsupportedOperationException, 可以使用ByteBuf.hasArray()来检查是否支持访问数组。
  • Direct Buffer( 直接缓冲区)
    直接缓冲区,在堆之外直接分配内存。直接缓冲区不会占用堆空间容量,使用时应该考虑到应用程序要使用的最大内存容量以及如何限制它。直接缓冲区在使用 Socket 传递数据时性能很好,因为若使用间接缓冲区,JVM 会先将数据复制到直接缓冲区再进行传递;但是直接缓冲区的缺点是在分配内存空间和释放内存时比堆缓冲区更复杂, 而 Netty使用内存池来解决这样的问题,这也是 Netty 使用内存池的原因之一。直接缓冲区不支持数组访问数据,但是我们可以间接的访问数据数组,如下面代码:
    ByteBuf directBuf =  Unpooled.directBuffer(16);
if(!directBuf.hasArray()){
int len = directBuf.readableBytes();
byte[] arr = new byte[len];
directBuf.getBytes(0, arr);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • Composite Buffer(复合缓冲区)
    复合缓冲区,我们可以创建多个不同的 ByteBuf,然后提供一个这些 ByteBuf
    组合的视图。复合缓冲区就像一个列表,我们可以动态的添加和删除其中的 ByteBuf,JDK 的ByteBuffer 没有这样的功能。Netty 提供了 CompositeByteBuf 类来处理复合缓冲区,CompositeByteBuf只是一个视图,CompositeByteBuf.hasArray()总是返回 false,因为它 可能包含一些直接或间接的不同类型的 ByteBuf。下面是使用 CompositeByteBuf 的例子:
 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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

CompositeByteBuf是ByteBuf的子类, 我们可以像操作BytBuf一样操作CompositeByteBuf。并且 Netty 优化套接字读写的操作是尽可能的使用 CompositeByteBuf 来做的,使用CompositeByteBuf不会操作内存泄露问题。

ByteBuf 字节操作API:

  • 随机访问索引
    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));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意通过索引访问时不会推进读索引和写索引,我们可以通过 ByteBuf 的
readerIndex()或writerIndex()来分别推进读索引或写索引。

  • 顺序访问索引
    ByteBuf 提供两个指针变量支付读和写操作,读操作是使用 readerIndex(),写操作时使用 writerIndex()。这和 JDK 的 ByteBuffer 不同,ByteBuffer 只有一个方法来设置索引,所以需要使用 flip()方法来切换读和写模式。 ByteBuf 一定符合:
    0 <= readerIndex <= writerIndex <= capacity。

  • Discardable bytes废弃字节
    我们可以调用 ByteBuf.discardReadBytes()来回收已经读取过的字节, discardReadBytes()将丢弃从索引 0 到 readerIndex 之间的字节。
    ByteBuf.discardReadBytes()可以用来清空 ByteBuf 中已读取的数据, 从而使ByteBuf有多余的空间容纳新的数据, 但是 discardReadBytes()可能会涉及内存复制, 因为它需要移动 ByteBuf 中可读的字节到开始位置, 这样的操作会影响性能, 一般在需要马上释放内存的时候使用收益会比较大。

  • 可读字节( 实际内容)
    任何读操作会增加 readerIndex,如果读取操作的参数也是一个 ByteBuf
    而没有指定目的索引,指定的目的缓冲区的 writerIndex 会一起增加,没有足够的内容时会抛出IndexOutOfBoundException。 新分配、 包装、 复制的缓冲区的 readerIndex 的默认值都是 0。下面代码显示了获取所有可读数据:

   ByteBuf buf = Unpooled.buffer(16);
while(buf.isReadable()){
System.out.println(buf.readByte());
}
  • 1
  • 2
  • 3
  • 4
  • 可写字节
    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());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 清除缓冲区索引
    Clearing the buffer indexs 调用 ByteBuf.clear()可以设置 readerIndex 和 writerIndex 为 0,clear()不会清除缓冲区的内容, 只是将两个索引值设置为 0。 请注意 ByteBuf.clear()与 JDK 的 ByteBuffer.clear()的语义不同。 和 discardReadBytes()相比,clear()是便宜的,因为 clear()不会复制任何内存。
  • 搜索操作
    Search operations 各种 indexOf()方法帮助你定位一个值的索引是否符合,我们可以用ByteBufProcessor 复杂动态顺序搜索实现简单的静态单字节搜索。如果你想解码可变长度的数据,如 null 结尾的字符串,你会发现 bytesBefore(byte value)方法有用。例如我们写一个集成的 flash sockets 的应用程序,这个应用程序使用 NULL 结束的内容,使用bytesBefore(byte value)方法可以很容易的检查数据中的空字节。没有 ByteBufProcessor的话,我们需要自己做这些事情,使用 ByteBufProcessor 效率更好。
  • 标准和重置 Mark and reset 每个 ByteBuf 有两个标注索引,一个存储 readerIndex,一个存储 writerIndex。你可以通过调用一个重置方法重新定位两个索引之一, 它类似于 InputStream 的标注和重置方法,没有读限制。 我们可以通过调用 readerIndex(int readerIndex)和 writerIndex(int writerIndex) 移动读索引和写索引到指定位置,调用这两个方法设置指定索引位置时可能抛出IndexOutOfBoundException。 衍生的缓冲区Derived buffers 调用 duplicate()、slice()、slice(int index, int length)、order(ByteOrder endianness)会创建一个现有缓冲区的视图。衍生的缓冲区有独立的 readerIndex、writerIndex 和标注索引。如果需要现有缓冲区的全新副本,可以使用 copy()或 copy(int index, int length)获得。
 // 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));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 读/ 写操作以及其他一些操作 有两种主要类型的读写操作:

    1. get/set 操作以索引为基础,在给定的索引设置或获取字节
    2. 从当前索引开始读写,递增当前的写索引或读索引

二: ByteBufHolder

ByteBufHolder 是一个辅助类,是一个接口,其实现类是DefaultByteBufHolder,还有一些实现了ByteBufHolder 接口的其他接口类。ByteBufHolder 的作用就是帮助更方便的访问 ByteBuf 中的数据, 当缓冲区没用了后, 可以使用这个辅助类释放资源。 ByteBufHolder很简单,提供的可供访问的方法也很少。如果你想实现一个“消息对象”有效负载存储在ByteBuf,使用ByteBufHolder 是一个好主意。

尽管 Netty 提供的各种缓冲区实现类已经很容易使用,但 Netty 依然提供了一些使用的工具类, 使得创建和使用各种缓冲区更加方便。 下面会介绍一些 Netty 中的缓冲区工具类。

  • ByteBufAllocator Netty 支持各种 ByteBuf 的池实现,来使 Netty 提供一种称为ByteBufAllocator 成为可能。ByteBufAllocator 负责分配 ByteBuf 实例,ByteBufAllocator 提供了各种分配不同ByteBuf 的方法,如需要一个堆缓冲区可以使用 ByteBufAllocator.heapBuffer(),需要一个直接缓冲区可以使用ByteBufAllocator.directBuffer(),需要一个复合缓冲区可以使用 ByteBufAllocator.compositeBuffer()。 其他方法的使用可以看ByteBufAllocator 源码及注释。 获取 ByteBufAllocator 对象很容易,可以从 Channel 的 alloc()获取,也可以从ChannelHandlerContext 的 alloc()获取。看下面代码:
 ServerBootstrap b = new ServerBootstrap();
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()
ctx.writeAndFlush(buf.duplicate()).addListener(
ChannelFutureListener.CLOSE);
}
});
}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Netty 有两种不同的 ByteBufAllocator 实现,一个实现 ByteBuf实例池将分配和回收成本以及内存使用降到最低;另一种实现是每次使用都创建一个新的 ByteBuf 实例。Netty默认使用PooledByteBufAllocator, 我们可以通过 ChannelConfig 或通过引导设置一个不同的实现来改变。更多细节在后面讲述。

  1. Unpooled
    Unpooled也是用来创建缓冲区的工具类,Unpooled 的使用也很容易。Unpooled 提供了很多方法,详细方法及使用可以看 API 文档或 Netty 源码。看下面代码:
  //创建复合缓冲区
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
//创建堆缓冲区
ByteBuf heapBuf = Unpooled.buffer(8);
//创建直接缓冲区
ByteBuf directBuf = Unpooled.directBuffer(16);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. ByteBufUtil
    ByteBufUtil 提供了一些静态的方法,在操作 ByteBuf 时非常有用。ByteBufUtil 提供了 Unpooled 之外的一些方法,也许最有价值的是 hexDump(ByteBuf buffer)方法,这个方法返回指定 ByteBuf 中可读字节的十六进制字符串, 可以用于调试程序时打印 ByteBuf 的内容,十六进制字符串相比字节而言对用户更友好。

Netty 缓冲 API 提供了几个优势

    1. 可以自定义缓冲类型

    2. 通过一个内置的复合缓冲类型实现零拷贝

    3. 扩展性好,比如 StringBuffer

    4. 不需要调用 flip()来切换读/写模式

    5. 读取和写入索引分开

    6. 方法链

    7. 引用计数

    8. Pooling(池)

Netty 缓存buffer介绍及使用的更多相关文章

  1. .Net环境下的缓存技术介绍 (转)

    .Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 ...

  2. .Net环境下的缓存技术介绍

    .Net环境下的缓存技术介绍 摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 1.1 ...

  3. CYQ.Data V5 分布式自动化缓存设计介绍(二)

    前言: 最近一段时间,开始了<IT连>创业,所以精力和写的文章多数是在分享创业的过程. 而关于本人三大框架CYQ.Data.Aries.Taurus.MVC的相关文章,基本都很少写了. 但 ...

  4. Netty重要概念介绍

    Netty重要概念介绍 Bootstrap Netty应用程序通过设置bootstrap(引导)类开始,该类提供了一个用于网络成配置的容器. 一种是用于客户端的Bootstrap 一种是用于服务端的S ...

  5. asp.net缓存使用介绍

    介绍: 在我解释cache管理机制时,首先让我阐明下一个观念:IE下面的数据管理.每个人都会用不同的方法去解决如何在IE在管理数据.有的会提到用状态管理,有的提到的cache管理,这里我比较喜欢cac ...

  6. 小D课堂 - 零基础入门SpringBoot2.X到实战_第9节 SpringBoot2.x整合Redis实战_37、分布式缓存Redis介绍

    笔记 1.分布式缓存Redis介绍      简介:讲解为什么要用缓存和介绍什么是Redis,新手练习工具          1.redis官网 https://redis.io/download   ...

  7. Netty——基本使用介绍

    https://blog.csdn.net/haoyuyang/article/details/53243785 1.为什么选择Netty 上一篇文章我们已经了解了Socket通信(IO/NIO/AI ...

  8. netty(六) buffer 源码分析

    问题 : netty的 ByteBuff 和传统的ByteBuff的区别是什么? HeapByteBuf 和 DirectByteBuf 的区别 ? HeapByteBuf : 使用堆内存,缺点 ,s ...

  9. 003——Netty之Buffer、Channel以及多路复用器Selector

    Buffer 1.缓冲区类型 2.缓冲区定义 (1)Buffer是一个对象,其中包含写入与读出的数据.是新IO与原IO的重要区别.任何情况下访问NIO中的数据都需要通过缓存区进行操作. (2)Buff ...

  10. 1.2_springboot2.x中redis缓存&原理介绍

    1.整合redis作为缓存 说明这里springboot版本2.19 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构 ...

随机推荐

  1. torch和numpy的相互转换

    import torch x = torch.rand(2,2) x1 = x.numpy() # torch转换到numpy x2 = torch.from_numpy(x1) #numpy转换to ...

  2. 关于 vue3 中的 fragment 组件

    vue3 中的模板中只能返回一个元素 ,否则报错,使用 fragment 组件可以返回多个元素标签

  3. 60 .vue的生命周期和小程序的生命周期区别

    https://blog.csdn.net/weixin_43359799/article/details/123137288

  4. Android复习(三)清单文件中的元素——>uses-sdk

    <uses-sdk> Google Play 会利用在应用清单中声明的 <uses-sdk> 属性,从不符合其平台版本要求的设备上滤除您的应用.在设置这些属性前,请确保您了解  ...

  5. 关于 KubeSphere IDOR 安全漏洞 CVE-2024-46528 的声明及解决方案

    近期,有第三方平台的安全技术人员发现了在 KubeSphere 开源版 3.4.1 及 4.1.1 上存在不安全的直接对象引用(IDOR)的漏洞,该漏洞允许低权限的通过认证的攻击者在没有适当授权检查的 ...

  6. 一文读懂 Prometheus 长期存储主流方案

    嘉宾 | 霍秉杰 整理 | 西京刀客 出品 | CSDN 云原生 Prometheus 作为云原生时代崛起的标志性项目,已经成为可观测领域的事实标准.Prometheus 是单实例不可扩展的,那么如果 ...

  7. 在C#中基于Semantic Kernel的检索增强生成(RAG)实践

    Semantic Kernel简介 玩过大语言模型(LLM)的都知道OpenAI,然后微软Azure也提供了OpenAI的服务:Azure OpenAI,只需要申请到API Key,就可以使用这些AI ...

  8. MongoDB mongod.log "connection refused because too many open connections" 处理方法

    一.MongoDB副本集 副本集名称 角色 IP地址 端口号 优先级 CCTV-test Primary 192.168.1.21 27017 10 Secondary 192.168.1.21 27 ...

  9. 你的第一个Solana SPL

    简介 TFT 你的第一个SPL The first token 技术栈和库 Rust Anchor框架 Typescript(测试) 开发环境和其它网络地址 DevNet: https://api.d ...

  10. Nuxt.js 应用中的 nitro:init 事件钩子详解

    title: Nuxt.js 应用中的 nitro:init 事件钩子详解 date: 2024/11/3 updated: 2024/11/3 author: cmdragon excerpt: n ...