转载请注明出处 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 含几个有重要的属性

final ByteBuf srcBuf; // the originally added buffer
int srcAdjustment; // index of the start of this CompositeByteBuf relative to srcBuf
int offset; // offset of this component within this CompositeByteBuf
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

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

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

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

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

然后新建新的组件操作

@SuppressWarnings("deprecation")
private Component newComponent(final ByteBuf buf, final int offset) {
final int srcIndex = buf.readerIndex();
final int len = buf.readableBytes(); // unpeel any intermediate outer layers (UnreleasableByteBuf, LeakAwareByteBufs, SwappedByteBuf)
ByteBuf unwrapped = buf;
int unwrappedIndex = srcIndex;
while (unwrapped instanceof WrappedByteBuf || unwrapped instanceof SwappedByteBuf) {
unwrapped = unwrapped.unwrap();
} // unwrap if already sliced
if (unwrapped instanceof AbstractUnpooledSlicedByteBuf) {
unwrappedIndex += ((AbstractUnpooledSlicedByteBuf) unwrapped).idx(0);
unwrapped = unwrapped.unwrap();
} else if (unwrapped instanceof PooledSlicedByteBuf) {
unwrappedIndex += ((PooledSlicedByteBuf) unwrapped).adjustment;
unwrapped = unwrapped.unwrap();
} else if (unwrapped instanceof DuplicatedByteBuf || unwrapped instanceof PooledDuplicatedByteBuf) {
unwrapped = unwrapped.unwrap();
} // We don't need to slice later to expose the internal component if the readable range
// is already the entire buffer
// 如果整个buf都是可以被读取的 则保存一个切片
final ByteBuf slice = buf.capacity() == len ? buf : null; return new Component(buf.order(ByteOrder.BIG_ENDIAN), srcIndex,
unwrapped.order(ByteOrder.BIG_ENDIAN), unwrappedIndex, offset, len, slice);
}

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

Component(ByteBuf srcBuf, int srcOffset, ByteBuf buf, int bufOffset,
int offset, int len, ByteBuf slice) {
this.srcBuf = srcBuf;
this.srcAdjustment = srcOffset - offset;
this.buf = buf;
this.adjustment = bufOffset - offset;
this.offset = offset;
this.endOffset = offset + len;
this.slice = slice;
}

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

以下面的方法为例

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

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


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

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

@Override
public CompositeByteBuf setByte(int index, int value) {
Component c = findComponent(index);
c.buf.setByte(c.idx(index), value);
return this;
}

参考

《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. 20191225--python学习第二天笔记(补)

    1.内容回顾 学习计算机基础 安装解释器 2.语法 print/input 整型 int/字符串 str/布尔类型 boolen 条件语句 and运算符 变量 3.练习 评分规则:用户输入成绩,根据成 ...

  2. k8s系列---基于canal的网络策略

    文章拷自:http://blog.itpub.net/28916011/viewspace-2215383/ 加上自己遇到的问题简单记录 安装文档:https://docs.projectcalico ...

  3. ELK logstash 各种报错

    1.logstash 启动后数据传输了,但是 ElasticSearch 中没有生成索引,查看logstash日志,报错如下 [2018-06-08T14:46:25,387][WARN ] [log ...

  4. echarts 的 formatter用法

    前言:formatter格式化方法.使用formatter调用自定义的数据,把内容通过处理让变成我们想要的样子. 比如,echarts数据显示是这样的(bug:部分内容被隐藏掉了,显示太长,不美观) ...

  5. Hibernate入门之创建数据库表

    前言 Hibernate 5.1和更早版本至少需要Java 1.6和JDBC 4.0,Hibernate 5.2和更高版本至少需要Java 1.8和JDBC 4.2,从本节开始我们正式进入Hibern ...

  6. 将jsp页面转化为图片或pdf升级版(二)(qq:1324981084)

    java高级架构师全套vip教学视频,需要的加我qq1324981084 上面我们已经将jsp页面转化成html页面了,那么接下来我们的目标是利用这个html页面形成pdf或图片格式.这里我用到的是w ...

  7. 获取Data和Log默认路径

    使用SERVERPROPERTY()来得到Data和Log的默认路径: InstanceDefaultDataPath和InstanceDefaultLogPath分别返回默认数据和日志目录. DEC ...

  8. 疫情之下,使用FRP实现内网穿透,远程连接公司电脑进行办公

    当前情况下,经常会有需要到公司电脑进行一些操作,比如连接内网OA,数据库或者提交文档.为了减少外出,将使用frp进行内网穿透的方法进行一个说明. 前提条件 1. 一台拥有公网 IP 的设备(如果没有, ...

  9. myeclipse 2018 intaslled jars JREs 选项区别,及注意事项

    Standard 1.1.x VM与Standard VM的区别 在Eclipse或MyEclipse中要设置Installed JREs时,有三个选择: - Execution Environmen ...

  10. Lua实现的八皇后问题

    来自<Lua程序与设计>第二节- 八皇后问题 输出所有解的解法 书中提供的源代码,加注了自己的注释. N = 8 --[[ N为棋盘规模 a为一维数组,保存第i个皇后所在的列数 ]] -- ...