netty AbstractByteBuf类

ByteBuf和ByteBuffer的关系

Netty的ByteBuf及其子类可以看做是对JDK提供的ByteBuffer的一种扩展,JDK提供的ByteBuffer类有如下缺点:

  1. 长度固定,ByteBuffer类一旦被创建则它的容量是不能被改变的
  2. 只有一个标识位置的指针position,对于读写操作其提供的API比较复杂,编程复杂度较高

ByteBuf通过提供自动扩容的方法解决了ByteBuffer长度不能动态扩展的缺点,并引入了读索引(ReaderIndex)和写索引(WriterIndex)简化了读写操作。此外,ByteBuf还提供了诸如防止内存泄漏,池化等其他更高级的能力。

ByteBuf抽象类的类结构图

在这里插入图片描述

从内存分类的角度

​ ByteBuf的子类主要分为直接内存和堆内存两种类型 。

​ 直接内存:少了将数据从内核空间到复制到用户空间的过程,故其向socket读写数据的速度快,但他的分配和回收速度较慢

​ 堆内存:分配和回收速度较快,但向sokcet读写速度较慢

从内存回收的角度

​ 池化和非池化

AbstractByteBuf类

属性

//内存泄漏探测器
static final ResourceLeakDetector<ByteBuf> leakDetector =
        ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);

int readerIndex;//读索引
int writerIndex;//写索引
private int markedReaderIndex;//被标记的读索引
private int markedWriterIndex;//被标记的写索引
private int maxCapacity;//最大容量

构造函数

protected AbstractByteBuf(int maxCapacity) {
    checkPositiveOrZero(maxCapacity, "maxCapacity");//校验最大容量是否大于0
    this.maxCapacity = maxCapacity;
}

读操作

//将ByteBuf中length长度个字节读取到dst数组中,dst的索引从dstIndex开始
@Override
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
    checkReadableBytes(length);//校验可读的字节数是否大于length
    //真正用于数据读取的操作,因为每个子类的实现细节不同,所以在抽象类AbstractByteBuf中不做实现,交由具体		//的子类实现
    getBytes(readerIndex, dst, dstIndex, length);
    //对读索引进行递增
    readerIndex += length;
    return this;
}

checkReadableBytes(int minimumReadableBytes)方法的实现代码如下,用于校验合法性。

protected final void checkReadableBytes(int minimumReadableBytes) {
    checkReadableBytes0(checkPositiveOrZero(minimumReadableBytes, "minimumReadableBytes"));
}

/**
* 校验ByteBuf可读的字节数是否大于length,如果writerIndex - readerIndex < minimumReadableBytes,则会抛出* 索引越界异常
*/
private void checkReadableBytes0(int minimumReadableBytes) {
    ensureAccessible();
    if (checkBounds && readerIndex > writerIndex - minimumReadableBytes) {
        throw new IndexOutOfBoundsException(String.format(
                "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                readerIndex, minimumReadableBytes, writerIndex, this));
    }
}

protected final void ensureAccessible() {
    if (checkAccessible && !isAccessible()) {
        throw new IllegalReferenceCountException(0);
    }
}

写操作

将src中length长度个字节写入到ByteBuf中,dst的索引从srcIndex开始

@Override
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
    ensureWritable(length);//写操作校验
    setBytes(writerIndex, src, srcIndex, length);//执行具体的写操作
    writerIndex += length;//递增写索引
    return this;
}

写操作校验ensureWritable(int minWritableBytes)

@Override
public ByteBuf ensureWritable(int minWritableBytes) {
    ensureWritable0(checkPositiveOrZero(minWritableBytes, "minWritableBytes"));
    return this;
}

final void ensureWritable0(int minWritableBytes) {
    final int writerIndex = writerIndex();
    final int targetCapacity = writerIndex + minWritableBytes;//计算写入数据后的容量
    if (targetCapacity <= capacity()) {//如果写入数据后的容量小于总容量则校验通过
        ensureAccessible();
        return;
    }
    //如果写入后的容量超过ByteBuf的最大容量,则抛出异常
    if (checkBounds && targetCapacity > maxCapacity) {
        ensureAccessible();
        throw new IndexOutOfBoundsException(String.format(
            "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
            writerIndex, minWritableBytes, maxCapacity, this));
    }
	//计算ByteBuf目前可写的容量,等于capacity-writerIndex
    final int fastWritable = maxFastWritableBytes();
    int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
        : alloc().calculateNewCapacity(targetCapacity, maxCapacity);

    capacity(newCapacity);
}

ByteBuf扩容的逻辑如下:

当minNewCapacity小于阈值4M时,扩容的逻辑是以64开始进行翻倍,如果minNewCapacity大于4M,则不再执行翻倍的逻辑,而是每次对容量加4M。

总结:先翻倍,后步进

/**
* minNewCapacity:写入数据所需要的容量
* maxCapacity:ByteBuf的最大容量
*/
@Override
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
    checkPositiveOrZero(minNewCapacity, "minNewCapacity");
    if (minNewCapacity > maxCapacity) {
        throw new IllegalArgumentException(String.format(
                "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
                minNewCapacity, maxCapacity));
    }
    final int threshold = CALCULATE_THRESHOLD; // 4 MiB

    if (minNewCapacity == threshold) {
        return threshold;
    }

    // If over threshold, do not double but just increase by threshold.
    if (minNewCapacity > threshold) {
        int newCapacity = minNewCapacity / threshold * threshold;
        if (newCapacity > maxCapacity - threshold) {
            newCapacity = maxCapacity;
        } else {
            newCapacity += threshold;
        }
        return newCapacity;
    }

    // Not over threshold. Double up to 4 MiB, starting from 64.
    int newCapacity = 64;
    while (newCapacity < minNewCapacity) {
        newCapacity <<= 1;
    }

    return Math.min(newCapacity, maxCapacity);
}

操作索引

设置读索引
@Override
public ByteBuf readerIndex(int readerIndex) {
    if (checkBounds) {
        checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    this.readerIndex = readerIndex;
    return this;
}
设置写索引
@Override
public ByteBuf writerIndex(int writerIndex) {
    if (checkBounds) {
        checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    this.writerIndex = writerIndex;
    return this;
}
同时设置
@Override
public ByteBuf setIndex(int readerIndex, int writerIndex) {
    if (checkBounds) {
        checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    setIndex0(readerIndex, writerIndex);
    return this;
}

丢弃已读数据

丢弃已读数据的逻辑如下:

1、如果读索引readerIndex为0,则表示没有数据被读取,所以也无需做任何操作

2、如果读索引0 < readerIndex < writerIndex,则使用复制的方式将已读数据丢弃,并将读写索引修改为合适的值

3、如果0 < readerIndex = writerIndex,为情况2的特殊情况,直接将读写索引设置为0,并不擦除数据

@Override
public ByteBuf discardReadBytes() {
    if (readerIndex == 0) {
        ensureAccessible();
        return this;
    }

    if (readerIndex != writerIndex) {
        setBytes(0, this, readerIndex, writerIndex - readerIndex);
        writerIndex -= readerIndex;
        adjustMarkers(readerIndex);
        readerIndex = 0;
    } else {
        ensureAccessible();
        adjustMarkers(readerIndex);
        writerIndex = readerIndex = 0;
    }
    return this;
}

setBytes方法

/**
* 将src的指定长度(从srcIndex开始,长度为length)的数据复制到当前ByteBuf的中(从index开始,长度为length)
*
*/
public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length);

调整mark指针的方法,adjustMarkers(int decrement)方法的逻辑如下:

1、如果被标记的读写索引都小于decrement,则直接将被标记的读写索引(markedReaderIndex,markedWriterIndex)置为0

2、如果被标记的读索引(markedReaderIndex)小于decrement,而写索引大于decrement,则将markedReaderIndex置为0,markedWriterIndex为 markedWriterIndex - decrement

3、如果读写索引都大于decrement,则读写索引都减去decrement即可

/**
* 
*/
protected final void adjustMarkers(int decrement) {
    int markedReaderIndex = this.markedReaderIndex;//已标记的读索引
    if (markedReaderIndex <= decrement) {
        this.markedReaderIndex = 0;
        int markedWriterIndex = this.markedWriterIndex;
        if (markedWriterIndex <= decrement) {
            this.markedWriterIndex = 0;
        } else {
            this.markedWriterIndex = markedWriterIndex - decrement;
        }
    } else {
        this.markedReaderIndex = markedReaderIndex - decrement;
        markedWriterIndex -= decrement;
    }
}

跳过指定长度的数据

直接递增度索引即可

@Override
public ByteBuf skipBytes(int length) {
    checkReadableBytes(length);
    readerIndex += length;
    return this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值