Netty中CompositeByteBuf的理解以及读写操作
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
Netty中CompositeByteBuf的理解以及读写操作的更多相关文章
- 第9.11节 Python中IO模块文件打开读写操作实例
为了对前面学习的内容进行一个系统化的应用,老猿写了一个程序来进行文件相关操作功能的测试. 一. 测试程序说明 该程序允许测试人员选择一个文件,自己输入文件打开模式.写入文件的位置以及写入内容,程序按照 ...
- !!无须定义配置文件中的每个变量的读写操作,以下代码遍历界面中各个c#控件,自动记录其文本,作为配置文件保存
namespace PluginLib{ /// <summary> /// 遍历控件所有子控件并初始化或保存其值 /// </summary> pub ...
- Python中关于txt的简单读写模式与操作
Python中关于txt的简单读写操作 常用的集中读写模式: 1.r 打开只读文件,该文件必须存在. 2.r+ 打开可读写的文件,该文件必须存在. 3.w 打开只写文件,若文件存在则文件长度清为0,即 ...
- io流对文件读写操作
public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedRead ...
- INI 文件的读写操作
在C#中对INI文件进行读写操作,在此要引入using System.Runtime.InteropServices; 命名空间,具体方法如下: #region 变量 private static r ...
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
ASP.NET MVC Filters 4种默认过滤器的使用[附示例] 过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...
- 理解Netty中的零拷贝(Zero-Copy)机制【转】
理解零拷贝 零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢? WIKI中对其有如下定义: “Zero-copy” describes computer operations in which ...
- 一个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 ...
- berkerly db 中简单的读写操作(有一些C的 还有一些C++的)
最近在倒腾BDB,才发现自己确实在C++这一块能力很弱,看了一天的api文档,总算是把BDB的一些api之间的关系理清了,希望初学者要理清数据库基本知识中的环境,句柄,游标的基本概念,这样有助于你更好 ...
随机推荐
- 跨域的两种解决方法jsonp和CORS
1.跨域 什么是跨域? 当你请求的url是不同源的数据的时候,浏览器一般会抛出请求跨域的错误,如下图: 造成跨域的原因? 即你违反了浏览器的同源策略的限制=>阻止一个域的js脚本和另外一个域的内 ...
- asp.net mvc项目实战遇见问题及解决方式----ajax请求500错误,请求多表数据
ajax请求出现500错误——但是想实现的功能是,把一个页面分成了两份,点击右边导航栏,利用ajax请求,请求数据,在右边出现相应页面,当时使用的是partialAction然后出现了这个500错误, ...
- drf认证组件、权限组件、jwt认证、签发、jwt框架使用
目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...
- PPT导出为图片
使用Aspose组件导出 Aspose有Aspose.Slides.dll,可以无需安装office,进行读写PPT文件. Aspose可能通过Aspose.Slides.NET安装 简单的导出图片d ...
- 《C# 6.0 本质论》 - 学习笔记
<C# 6.0 本质论> ========== ========== ==========[作者] (美) Mark Michaelis (美) Eric Lippert[译者] (中) ...
- C# 多线程之通过Timer开启线程的例子
本例通过Timer的tick()方法触发TimerCallback委托来开辟新的线程,线程中的具体工作通过一个静态方法作为参数给TimerCallback委托. using System; using ...
- django 定时任务 django-crontab 的使用
成功例子如下图: 1.前言 在做 django 开发需求时,多多少少都会遇到需要定时任务的功能,比如定时执行任务,检查订单之类的.可能是一段时间,比如每隔 10分钟执行一次,也可能是定点时间,比如 1 ...
- Docker基础(1) 原理篇
Docker是什么 Docker的构成 Docker的分层和写时拷贝策略 Docker与主流虚拟机的区别 Docker镜像与容器的关系 镜像的变更管理 Docker是什么 Docker是一个开源的应用 ...
- POJ-2299 Ultra-QuickSort(用树状数组求逆序对数)
题目链接 ac代码 #include<iostream> #include<cstdio> #include<cstring> #include<algori ...
- IntelliJ IDEA 更新
一. 下载最新版本的idea 1. 官网下载,官网地址:http://www.jetbrains.com/idea/download/#section=windows 2. 百度网盘直接下载:http ...