Netty源码分析第5章(ByteBuf)---->第1节: AbstractByteBuf
Netty源码分析第五章: ByteBuf
概述:
熟悉Nio的小伙伴应该对jdk底层byteBuffer不会陌生, 也就是字节缓冲区, 主要用于对网络底层io进行读写, 当channel中有数据时, 将channel中的数据读取到字节缓冲区, 当要往对方写数据的时候, 将字节缓冲区的数据写到channel中
但是jdk的byteBuffer是使用起来有诸多不便, 比如只有一个标记位置的指针position, 在进行读写操作时要频繁的通过flip()方法进行指针位置的移动, 极易出错, 并且byteBuffer的内存一旦分配则不能改变, 不支持动态扩容, 当读写的内容大于缓冲区内存时, 则会发生索引越界异常
而Netty的ByteBuf对jdk的byteBuffer做了重新的定义, 同样是字节缓冲区用于读取网络io中的数据, 但是使用起来大大简化, 并且支持了自动扩容, 不用担心读写数据大小超过初始分配的大小
byteBuf根据其分类的不同底层实现方式有所不同, 有直接基于jdk底层byteBuffer实现的, 也有基于字节数组的实现的, 对于byteBuf的分类, 在后面的小节将会讲到
byteBuf中维护了两个指针, 一是读指针, 二是写指针, 两个指针相互独立, 在读操作的时候, 只会移动读指针, 通过指针位置记录读取的字节数
同样在写操作时, 也只会移动写指针, 通过写指针的位置记录写的字节数
在每次读写操作的过程中都会对指针的位置进行校验, 读指针的位置不能超过写指针, 否则会抛出异常
同样, 写指针不能超过缓冲区分配的内存, 则将对缓冲区做扩容操作
具体指针操作, 入下图所示:
5-0-1
第一节: AbstractByteBuf
在讲AbstractByteBuf之前, 我们首先先了解一下ByteBuf这个类, 这是所有ByteBuf的最顶层抽象, 里面定义了大量对ByteBuf操作的抽象方法供子类实现
AbstractByteBuf同样也缓冲区的抽象类, 定义了byteBuf的骨架操作, 比如参数校验, 自动扩容, 以及一些读写操作的指针移动, 但具体的实现, 不同的bytebuf实现起来是不同的, 这种情况则交给其子类实现
AbstractByteBuf继承了这个类, 并实现了其大部分的方法
首先看这个类的属性和构造方法:
//读指针
int readerIndex;
//写指针
int writerIndex;
//保存读指针
private int markedReaderIndex;
//保存写指针
private int markedWriterIndex;
//最大分配容量
private int maxCapacity; protected AbstractByteBuf(int maxCapacity) {
if (maxCapacity < 0) {
throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)");
}
this.maxCapacity = maxCapacity;
}
我们可以看到在属性中定义了读写指针的成员标量, 和读写指针位置的保存
在构造方法中可以传入可分配的最大内存, 然后赋值到成员变量中
我们看几个最简单的方法:
@Override
public int maxCapacity() {
return maxCapacity;
} @Override
public int readerIndex() {
return readerIndex;
} @Override
public int writerIndex() {
return writerIndex;
}
获取最大内存, 获取读写指针这些方法, 对所有的bytebuf都是通用的, 所以可以定义在AbstractByteBuf中
我们以一个writeBytes方法为例, 让同学们熟悉AbstractByteBuf中哪些部分自己实现, 哪些部分则交给了子类实现:
@Override
public ByteBuf writeBytes(ByteBuf src) {
writeBytes(src, src.readableBytes());
return this;
}
这个方法是将源的ByteBuf(参数)中的字节写入到自身ByteBuf中
首先这里调用了自身的writeBytes方法, 并传入参数ByteBuf本身, 以及Bytebuf的可读字节数, 我们跟到readbleBytes()方法中, 其实就是调用了自身的方法:
@Override
public int readableBytes() {
return writerIndex - readerIndex;
}
我们看到, 这里可读字节数就是返回了写指针到读指针之间的长度
我们再继续跟到writeBytes(src, src.readableBytes())中:
@Override
public ByteBuf writeBytes(ByteBuf src, int length) {
if (length > src.readableBytes()) {
throw new IndexOutOfBoundsException(String.format(
"length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
}
writeBytes(src, src.readerIndex(), length);
src.readerIndex(src.readerIndex() + length);
return this;
}
这里同样调用了自身的方法首先会对参数进行验证, 就是写入自身的长度不能超过源ByteBuf的可读字节数
这里又调用了一个wirte方法, 参数传入源Bytebuf, 其可读字节数, 写入的长度, 这里写入的长度我们知道就是源ByteBuf的可读字节数
我们再跟到writeBytes(src, src.readerIndex(), length);
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
ensureAccessible();
ensureWritable(length);
setBytes(writerIndex, src, srcIndex, length);
writerIndex += length;
return this;
}
我们重点关注第二个校验方法ensureWritable(length):
public ByteBuf ensureWritable(int minWritableBytes) {
if (minWritableBytes < 0) {
throw new IllegalArgumentException(String.format(
"minWritableBytes: %d (expected: >= 0)", minWritableBytes));
}
ensureWritable0(minWritableBytes);
return this;
}
然后我们再跟到ensureWritable0(minWritableBytes)方法中:
private void ensureWritable0(int minWritableBytes) {
if (minWritableBytes <= writableBytes()) {
return;
} if (minWritableBytes > maxCapacity - writerIndex) {
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
//自动扩容
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
capacity(newCapacity);
}
开始做了两个参数校验, 第一个表示当前ByteBuf写入的长度如果要小于可写字节数, 则返回
第二个可以换种方式去看minWritableBytes+ writerIndex> maxCapacity 也就是需要写入的长度+写指针必须要小于最大分配的内存, 否则报错, 注意这里最大分配内存不带表当前内存, 而是byteBuf所能分配的最大内存
如果需要写入的长度超过了可写字节数, 并且需要写入的长度+写指针不超过最大内存, 则就开始了ByteBuf非常经典也非常重要的操作, 也就是自动扩容
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
其中alloc()返回的是当前bytebuf返回的缓冲区分配器对象, 我们之后的小节会讲到, 这里调用了其calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity)方法为其扩容, 其中传入的参数writerIndex + minWritableBytes代表所需要的容量, maxCapacity为最大容量
我们跟到扩容的方法里面去:
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
//合法性校验
if (minNewCapacity < 0) {
throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)");
}
if (minNewCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
minNewCapacity, maxCapacity));
}
//阈值为4mb
final int threshold = 1048576 * 4; //最小需要扩容内存(总内存) == 阈值
if (minNewCapacity == threshold) {
//返回阈值
return threshold;
} //最小扩容内存>阈值
if (minNewCapacity > threshold) {
//newCapacity为需要扩容内存
int newCapacity = minNewCapacity / threshold * threshold;
//目标容量+阈值>最大容量
if (newCapacity > maxCapacity - threshold) {
//将最大容量作为新容量
newCapacity = maxCapacity;
} else {
//否则, 目标容量+阈值
newCapacity += threshold;
}
return newCapacity;
}
//如果小于阈值
int newCapacity = 64;
//目标容量<需要扩容的容量
while (newCapacity < minNewCapacity) {
//倍增
newCapacity <<= 1;
}
//目标容量和最大容量返回一个最小的
return Math.min(newCapacity, maxCapacity);
}
扩容相关的逻辑注释也写的非常清楚, 如果小于阈值(4mb), 采用倍增的方式, 如果大于阈值(4mb), 采用平移4mb的方式
我们回到writeBytes(ByteBuf src, int srcIndex, int length):
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
ensureAccessible();
ensureWritable(length);
setBytes(writerIndex, src, srcIndex, length);
writerIndex += length;
return this;
}
再往下看setBytes(writerIndex, src, srcIndex, length), 这里的参数的意思是从当前byteBuf的writerIndex节点开始写入, 将源缓冲区src的读指针位置, 写lenght个字节, 这里的方法中AbstractByteBuf类并没有提供实现, 因为不同类型的BtyeBuf实现的方式是不一样的, 所以这里交给了子类去实现
最后将写指针后移length个字节
最后我们回到writeBytes(ByteBuf src, int length)方法中:
public ByteBuf writeBytes(ByteBuf src, int length) {
if (length > src.readableBytes()) {
throw new IndexOutOfBoundsException(String.format(
"length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
}
writeBytes(src, src.readerIndex(), length);
src.readerIndex(src.readerIndex() + length);
return this;
}
当writeBytes(src, src.readerIndex(), length)写完之后, 通过src.readerIndex(src.readerIndex() + length)将源缓冲区的读指针后移lenght个字节
以上对AbstractByteBuf的简单介绍和其中写操作的方法的简单剖析
Netty源码分析第5章(ByteBuf)---->第1节: AbstractByteBuf的更多相关文章
- Netty源码分析第5章(ByteBuf)---->第4节: PooledByteBufAllocator简述
Netty源码分析第五章: ByteBuf 第四节: PooledByteBufAllocator简述 上一小节简单介绍了ByteBufAllocator以及其子类UnPooledByteBufAll ...
- Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述
Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...
- Netty源码分析第5章(ByteBuf)---->第6节: 命中缓存的分配
Netty源码分析第6章: ByteBuf 第六节: 命中缓存的分配 上一小节简单分析了directArena内存分配大概流程, 知道其先命中缓存, 如果命中不到, 则区分配一款连续内存, 这一小节带 ...
- Netty源码分析第5章(ByteBuf)---->第7节: page级别的内存分配
Netty源码分析第五章: ByteBuf 第六节: page级别的内存分配 前面小节我们剖析过命中缓存的内存分配逻辑, 前提是如果缓存中有数据, 那么缓存中没有数据, netty是如何开辟一块内存进 ...
- Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程
Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...
- Netty源码分析第5章(ByteBuf)---->第2节: ByteBuf的分类
Netty源码分析第五章: ByteBuf 第二节: ByteBuf的分类 上一小节简单介绍了AbstractByteBuf这个抽象类, 这一小节对其子类的分类做一个简单的介绍 ByteBuf根据不同 ...
- Netty源码分析第5章(ByteBuf)---->第3节: 缓冲区分配器
Netty源码分析第五章: ByteBuf 第三节: 缓冲区分配器 缓冲区分配器, 顾明思议就是分配缓冲区的工具, 在netty中, 缓冲区分配器的顶级抽象是接口ByteBufAllocator, 里 ...
- Netty源码分析第5章(ByteBuf)---->第8节: subPage级别的内存分配
Netty源码分析第五章: ByteBuf 第八节: subPage级别的内存分配 上一小节我们剖析了page级别的内存分配逻辑, 这一小节带大家剖析有关subPage级别的内存分配 通过之前的学习我 ...
- Netty源码分析第5章(ByteBuf)---->第9节: ByteBuf回收
Netty源码分析第五章: ByteBuf 第九节: ByteBuf回收 之前的章节我们提到过, 堆外内存是不受jvm垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf操作时, 使用完毕 ...
随机推荐
- Java基础加强之集合
集合整体框架图 各集合框架的概述 1. Collection(常用List和Set,不常用Queue和Vector),单元素集合. 2. Map(常用HashMap和TreeMap,不常用HashTa ...
- Mysql 多实例实施步骤
基本理论:利用同一套安装程序,不同配置文件,不同启动程序,不同数据目录.有公用资源,也有私有资源. 实现步骤: 1.正常安装mysql,二进制安装或者编译安装. 2.创建mysql多实例总目录,总目录 ...
- [转] Eclipse安装SVN插件
eclipse里安装SVN插件,一般来说,有三种方式: 1. 直接下载SVN插件,将其解压到eclipse的对应目录里 2. 使用eclipse 里Help菜单的“Install New Softwa ...
- ls: Call From hdoop2/192.168.18.87 to hdoop2:8020 failed on connection exception: java.net.ConnectException: 拒绝连接; For more details see
场景: 预发环境中,同事已经搭建了一套hadoop集群,由于版本与所需不符,所以需要替换版本 问题描述: 在配置文件都准确的情况下,启动hadoop,出现以下报错: 启动之前初始化: 初始化目录 ...
- ejs引擎项目
关于这个我也很懵逼,写这篇博客就是想记录一下,有哪位大神看到之后可以略微指点一二,不胜感激....... 一.项目结构 db model user.js version.js schema xx.js ...
- smtp发送html报告与日志附件图片png
1.非ssl发送: 授权码机制,开启smtp,获取授权码以qq邮箱为例: 附件展示: #!/usr/bin/python3 import os import smtplib from email.mi ...
- gitblit-1.8.0域认证
gitblit-1.8.0\data\defaults.properties # # DEFAULTS.PROPERTIES # # The default Gitblit settings. # # ...
- Eclipse设置格式化每行字符的长度
Windows>>prefrence>>Java>>CodeStyle>>formatter>>edit>>line wrapp ...
- vue+vux-ui+axios+mock搭建一个简单vue框架
1.首先感谢同事 2.之前一直在做angularjs的项目,目前vue火热,所以自己搭建了一个的vue框架,在此作为记录 vue+vux-ui这里就不介绍了,有很多博客都写的很详细了. 下面简单记录下 ...
- CPP strcat函数使用
strcat函数原型 char * strcat ( char * destination, const char * source ); strcat常见写法 // main.cpp // 字符数组 ...