转载请注明出处 https://www.cnblogs.com/majianming/articles/the_composite_byte_buf_read_and_wite_operation_of_netty.html

CompositeByteBuf实际上是一个虚拟化的ByteBuf,作为一个ByteBuf特殊的子类,可以用来对多个ByteBuf统一操作,一般情况下,CompositeByteBuf对多个ByteBuf操作并不会出现复制拷贝操作,只是保存原来ByteBuf的引用。


在正式开始介绍·CompositeByteBuf之前 需要先介绍一下CompositeByteBuf的一个重要的内部类Component .

(在CompositeByteBuf中保存有一个Component 类型的数组,这是整个CompositeByteBuf实现的关键数据结构。)

Component 称之为组件,是对原始ByteBuf的包装的数据结构

Component 含几个有重要的属性

  1. final ByteBuf srcBuf; // the originally added buffer
  2. int srcAdjustment; // index of the start of this CompositeByteBuf relative to srcBuf
  3. int offset; // offset of this component within this CompositeByteBuf
  4. int endOffset; // end offset of this component within this CompositeByteBuf
  • srcBuf

    指向原始的一个ByteBuf 保证不需要复制原始的ByteBuf 就可以达到读写的目的

    adjustment 标记经过Component包装后在整个现有坐标和现在坐标的偏移量

  • srcAdjustment

    srcBuf 的开始坐标相对于整个ConpositeByteBuf的相对坐标

  • offset

    标记经过Component包装后开始位置的坐标的实际坐标

  • endOffset

    标记经过Component包装后结束位置的坐标的实际坐标

所以在内部,只要通过offset和endOffset 将每一个component 所代表的ByteBuf 连接起来 就可以将全部的ByteBuf 视为一个ByteBuf

所以我们可以这样认为,在CompositeByteBuf 中,保存着一个包装有原始ByteBuf引用以及ByteBuf在当前的CompositeByteBuf 的相对位置的实例集合.

那么这个虚拟化的ByteBuf是如何生成的,以及是如何读写底层的数据结构的ByteBuf

我们来看一下其中的一个构造函数

  1. CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents,
  2. ByteBuf[] buffers, int offset) {
  3. this(alloc, direct, maxNumComponents, buffers.length - offset);
  4. // 第二个参数cIndex=0 表示新增加的buffers插入到0开始的位置 原来的组件往后移动
  5. addComponents0(false, 0, buffers, offset);
  6. // 如果需要 合并全部组件为一个组件
  7. consolidateIfNeeded();
  8. // 设置已经写满 不可再写 可以从头开始读取
  9. setIndex0(0, capacity());
  10. }

其中最重要的是addComponents0的这个函数,因为初始化时组件数组为空,所以我们应该从第一个位置开始新增新的组件

  1. // 将buffer从arrOffset开始的元素依此添加到cIndex开始的组件数组中
  2. private CompositeByteBuf addComponents0(boolean increaseWriterIndex,
  3. final int cIndex, ByteBuf[] buffers, int arrOffset) {
  4. final int len = buffers.length, count = len - arrOffset;//count 真正增加的数量
  5. // only set ci after we've shifted so that finally block logic is always correct
  6. int ci = Integer.MAX_VALUE;
  7. try {
  8. // 检查是否支持在cIndex位置开始添加
  9. checkComponentIndex(cIndex);
  10. // 如果是插入到现有集合的元素中的话 移动组件到合适的位置
  11. shiftComps(cIndex, count); // will increase componentCount
  12. // nextOffset 表示插入的第一个组件的所对应的byteBuf起始位置在整个CompositeByteBuf(实际上也是一个ByteBuf)的位置
  13. int nextOffset = cIndex > 0 ? components[cIndex - 1].endOffset : 0;
  14. for (ci = cIndex; arrOffset < len; arrOffset++, ci++) {
  15. ByteBuf b = buffers[arrOffset];
  16. if (b == null) {
  17. break;
  18. }
  19. // 新建一个组件用于存放 记录全局位置
  20. Component c = newComponent(ensureAccessible(b), nextOffset);
  21. // 保存到组件数组中
  22. components[ci] = c;
  23. // 下一个组件的起始位置等于上一个组件的endOffset 实际上是上一个组件的nextOffset+len
  24. nextOffset = c.endOffset;
  25. }
  26. return this;
  27. } finally {
  28. // ci is now the index following the last successfully added component
  29. if (ci < componentCount) {
  30. if (ci < cIndex + count) {
  31. // 如果添加完毕 还有部分组件的空间没有使用
  32. // 实际上是部分因为元素为空无法添加进来
  33. // 那么实际上在最后的地方是存在空的数组元素的 需要释放掉
  34. // we bailed early
  35. removeCompRange(ci, cIndex + count);
  36. for (; arrOffset < len; ++arrOffset) {
  37. // 释放掉没有添加到组件的其他byteBuf
  38. ReferenceCountUtil.safeRelease(buffers[arrOffset]);
  39. }
  40. }
  41. // 需要更新一下被插入元素的后续偏移量
  42. //
  43. updateComponentOffsets(ci); // only need to do this here for components after the added ones
  44. }
  45. // 如果需要更新写的坐标的 todo 写入正常? 是否正常都应该更新!
  46. // 需要添加写入的为真正添加的坐标
  47. if (increaseWriterIndex && ci > cIndex && ci <= componentCount) {
  48. writerIndex += components[ci - 1].endOffset - components[cIndex].offset;
  49. }
  50. }
  51. }

然后新建新的组件操作

  1. @SuppressWarnings("deprecation")
  2. private Component newComponent(final ByteBuf buf, final int offset) {
  3. final int srcIndex = buf.readerIndex();
  4. final int len = buf.readableBytes();
  5. // unpeel any intermediate outer layers (UnreleasableByteBuf, LeakAwareByteBufs, SwappedByteBuf)
  6. ByteBuf unwrapped = buf;
  7. int unwrappedIndex = srcIndex;
  8. while (unwrapped instanceof WrappedByteBuf || unwrapped instanceof SwappedByteBuf) {
  9. unwrapped = unwrapped.unwrap();
  10. }
  11. // unwrap if already sliced
  12. if (unwrapped instanceof AbstractUnpooledSlicedByteBuf) {
  13. unwrappedIndex += ((AbstractUnpooledSlicedByteBuf) unwrapped).idx(0);
  14. unwrapped = unwrapped.unwrap();
  15. } else if (unwrapped instanceof PooledSlicedByteBuf) {
  16. unwrappedIndex += ((PooledSlicedByteBuf) unwrapped).adjustment;
  17. unwrapped = unwrapped.unwrap();
  18. } else if (unwrapped instanceof DuplicatedByteBuf || unwrapped instanceof PooledDuplicatedByteBuf) {
  19. unwrapped = unwrapped.unwrap();
  20. }
  21. // We don't need to slice later to expose the internal component if the readable range
  22. // is already the entire buffer
  23. // 如果整个buf都是可以被读取的 则保存一个切片
  24. final ByteBuf slice = buf.capacity() == len ? buf : null;
  25. return new Component(buf.order(ByteOrder.BIG_ENDIAN), srcIndex,
  26. unwrapped.order(ByteOrder.BIG_ENDIAN), unwrappedIndex, offset, len, slice);
  27. }

调用Component的构造方法,将原始的byteBuf和经过解包的byteBuf已经相应的偏移值保存起来

  1. Component(ByteBuf srcBuf, int srcOffset, ByteBuf buf, int bufOffset,
  2. int offset, int len, ByteBuf slice) {
  3. this.srcBuf = srcBuf;
  4. this.srcAdjustment = srcOffset - offset;
  5. this.buf = buf;
  6. this.adjustment = bufOffset - offset;
  7. this.offset = offset;
  8. this.endOffset = offset + len;
  9. this.slice = slice;
  10. }

接下来看看给定一个的CompositeByteBuf实例的下标,如何读取一个字节

以下面的方法为例

  1. @Override
  2. public byte getByte(int index) {
  3. // 通过下标在组件数组中找到合适的组件实例
  4. Component c = findComponent(index);
  5. // 将下标转换为组件实例中(对应的ByteBuf)的下标读取
  6. return c.buf.getByte(c.idx(index));
  7. }
  1. private Component findComponent(int offset) {
  2. Component la = lastAccessed;
  3. // 如果最近访问过 那么最近读取这个组件可能就是我们要的
  4. // 一个缓存 优化 实际上也可以不要 性能差一些
  5. if (la != null && offset >= la.offset && offset < la.endOffset) {
  6. ensureAccessible();
  7. return la;
  8. }
  9. checkIndex(offset);
  10. // 二分查找
  11. return findIt(offset);
  12. }
  1. private Component findIt(int offset) {
  2. // 遍历组件 找到这个组件应该满足offset<=index<=endOffset
  3. for (int low = 0, high = componentCount; low <= high; ) {
  4. int mid = low + high >>> 1;
  5. Component c = components[mid];
  6. if (offset >= c.endOffset) {
  7. low = mid + 1;
  8. } else if (offset < c.offset) {
  9. high = mid - 1;
  10. } else {
  11. lastAccessed = c;
  12. return c;
  13. }
  14. }
  15. throw new Error("should not reach here");
  16. }

到这里已经找到那个合适的组件实例了 只要将全局的index 转换为组件实例的局部index 就可以获得对应的字节值返回了 即组件实例的idx方法


  1. int idx(int index) {
  2. // 加上相对位置的偏移量 这个实际是一个负值
  3. return index + adjustment;
  4. }

而写入也是一样的情况 ,找个合适的组件实例,转换为实例的局部下标 写入即可

  1. @Override
  2. public CompositeByteBuf setByte(int index, int value) {
  3. Component c = findComponent(index);
  4. c.buf.setByte(c.idx(index), value);
  5. return this;
  6. }

参考

《Netty 实战》

netty version 4.1.45.Final

转载请注明出处 https://www.cnblogs.com/majianming/articles/the_composite_byte_buf_read_and_wite_operation_of_netty.html

Netty中CompositeByteBuf的理解以及读写操作的更多相关文章

  1. 第9.11节 Python中IO模块文件打开读写操作实例

    为了对前面学习的内容进行一个系统化的应用,老猿写了一个程序来进行文件相关操作功能的测试. 一. 测试程序说明 该程序允许测试人员选择一个文件,自己输入文件打开模式.写入文件的位置以及写入内容,程序按照 ...

  2. !!无须定义配置文件中的每个变量的读写操作,以下代码遍历界面中各个c#控件,自动记录其文本,作为配置文件保存

    namespace PluginLib{    /// <summary>    /// 遍历控件所有子控件并初始化或保存其值    /// </summary>    pub ...

  3. Python中关于txt的简单读写模式与操作

    Python中关于txt的简单读写操作 常用的集中读写模式: 1.r 打开只读文件,该文件必须存在. 2.r+ 打开可读写的文件,该文件必须存在. 3.w 打开只写文件,若文件存在则文件长度清为0,即 ...

  4. io流对文件读写操作

    public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedRead ...

  5. INI 文件的读写操作

    在C#中对INI文件进行读写操作,在此要引入using System.Runtime.InteropServices; 命名空间,具体方法如下: #region 变量 private static r ...

  6. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  7. 理解Netty中的零拷贝(Zero-Copy)机制【转】

    理解零拷贝 零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢? WIKI中对其有如下定义: “Zero-copy” describes computer operations in which ...

  8. 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式

    Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...

  9. berkerly db 中简单的读写操作(有一些C的 还有一些C++的)

    最近在倒腾BDB,才发现自己确实在C++这一块能力很弱,看了一天的api文档,总算是把BDB的一些api之间的关系理清了,希望初学者要理清数据库基本知识中的环境,句柄,游标的基本概念,这样有助于你更好 ...

随机推荐

  1. 常用命令 find chmod

    find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find  [指定查找目录]  [查找规则]  [查找 ...

  2. Linux高级系统恢复技术

    一,MBR毁坏: 查看系统分区在那: 毁坏MBR: 如果没有重启动,可以直接恢复: 如果重启之后就不可启动系统,需要恢复系统: 出现这样的情况: force off关机,使用光盘启动,添加一个镜像光盘 ...

  3. light oj 1095 - Arrange the Numbers排列组合(错排列)

    1095 - Arrange the Numbers Consider this sequence {1, 2, 3 ... N}, as an initial sequence of first N ...

  4. dotnetcore3.1 WPF 实现多语言

    dotnetcore3.1 WPF 实现多语言 Intro 最近把 DbTool 从 WinForm 迁移到了 WPF,并更新到了 dotnet core 3.1,并实现了基于 Microsoft.E ...

  5. MySQL中的执行计划explain

    一.用法及定义: explain为sql的执行计划.在sql前面加上explain关键字即可 如:explain select * from tbl_emp; 名词解释: id:[操作表的顺序] 1. ...

  6. MYSQLl给用户授予数据库表权限

    给targetUserName用户授予databaseName单个数据库权限 grant all privileges on databaseName.* to targetUserName@&quo ...

  7. mybatis 测试输出SQL语句到控制台配置

    1: mybatis-config.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...

  8. .netcore 3.1高性能微服务架构:封装调用外部服务的接口方法--HttpClient客户端思路分析

    众所周知,微服务架构是由一众微服务组成,项目中调用其他微服务接口更是常见的操作.为了便于调用外部接口,我们的常用思路一般都是封装一个外部接口的客户端,使用时候直接调用相应的方法.webservice或 ...

  9. 《自拍教程19》aapt_apk信息查看工具

    aapt命令行工具介绍 aapt.exe(Linux/Ubuntu/imac操作系统下是未带后缀的aapt), 是android sdk自带的用于打包apk,解析apk的命令行工具软件. aapt.e ...

  10. 爬取豆瓣网图书TOP250的信息

    爬取豆瓣网图书TOP250的信息,需要爬取的信息包括:书名.书本的链接.作者.出版社和出版时间.书本的价格.评分和评价,并把爬取到的数据存储到本地文件中. 参考网址:https://book.doub ...