Netty源码分析第五章: ByteBufhtml
概述:数组
熟悉Nio的小伙伴应该对jdk底层byteBuffer不会陌生, 也就是字节缓冲区, 主要用于对网络底层io进行读写, 当channel中有数据时, 将channel中的数据读取到字节缓冲区, 当要往对方写数据的时候, 将字节缓冲区的数据写到channel中网络
可是jdk的byteBuffer是使用起来有诸多不便, 好比只有一个标记位置的指针position, 在进行读写操做时要频繁的经过flip()方法进行指针位置的移动, 极易出错, 而且byteBuffer的内存一旦分配则不能改变, 不支持动态扩容, 当读写的内容大于缓冲区内存时, 则会发生索引越界异常ide
而Netty的ByteBuf对jdk的byteBuffer作了从新的定义, 一样是字节缓冲区用于读取网络io中的数据, 可是使用起来大大简化, 而且支持了自动扩容, 不用担忧读写数据大小超过初始分配的大小源码分析
byteBuf根据其分类的不一样底层实现方式有所不一样, 有直接基于jdk底层byteBuffer实现的, 也有基于字节数组的实现的, 对于byteBuf的分类, 在后面的小节将会讲到this
byteBuf中维护了两个指针, 一是读指针, 二是写指针, 两个指针相互独立, 在读操做的时候, 只会移动读指针, 经过指针位置记录读取的字节数spa
一样在写操做时, 也只会移动写指针, 经过写指针的位置记录写的字节数指针
在每次读写操做的过程当中都会对指针的位置进行校验, 读指针的位置不能超过写指针, 不然会抛出异常code
一样, 写指针不能超过缓冲区分配的内存, 则将对缓冲区作扩容操做orm
具体指针操做, 入下图所示:
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的简单介绍和其中写操做的方法的简单剖析