Netty ByteBuf原理及其源码分析


 类图    


   缓冲区介绍

    当我们进行数据传输的时候,往往需要缓冲区。java NIO 中自带的提供的就是java.nio.Buffer

    但是由于java自带的过于复杂,而且自身也有一定的缺陷(定长,一个标识位position等)。Netty便提供的自己的缓冲ByteBuf

   

     Nio ByteBuffer 和  Netty ByteBuf 对比


    1.指针问题

     

public class Test2 {
    public static void main(String[] args) {
        String content = "hello,world";
        ByteBuffer byteBuffer = ByteBuffer.allocate(256);
        byteBuffer.put(content.getBytes());
        byteBuffer.flip();
        byte[] bufferValue = new byte[byteBuffer.remaining()];
        byteBuffer.get(bufferValue);
        System.out.println(new String(bufferValue));
    }
}

示例中就是一种比较常见的NIO操作 比较关键的代码 byteBuffer.flip();它会把limit设置为position的位置。否则读取到的将会是错误的内容。



     ByteBuf通过2个索引来维护缓冲区的读写操作读操作通过readerIndex,写操作通过writeIndex。

     他们的初始值都为0,数据的写入将导致writeIndex增加,数据的读取将会导致readerIndex增加。但是它不会操作writeIndex。读取之后在0和readIndex范围称之为discard。调用discardReadBytes方法。可以释放这部分空间。readIndex和writeIndex之间的数据为可读数据。writeIndex和limit之间的数据为可写的空间。由于读写由不同的指针来维护,这样就可以避免NIO中显示的调用flip()来切换不同的操作了。



2.定长问题

    操作NIO的时候,当我们对缓冲区put的时候,如果缓冲区空间不够,将会抛出异常。为了避免这个问题。Netty在write数据的时候,首先会对数据的长度和可写空间做个校验。如果不足,就会创建一个新的ByteBuf,并把之前的复制到新建的这个ByteBuf。最后释放老的ByteBuf。

 

下来,我们一起来追踪下源码


buffer.writeInt(1);

首先进入writeInt方法

@Override
public ByteBuf writeInt(int value) {
    ensureWritable(4);
    _setInt(writerIndex, value);
    writerIndex += 4;
    return this;
}

其中非常关键的一行 ensureWritable(4);netty就是通过这个方法达到扩容。我们继续往下追踪


public ByteBuf ensureWritable(int minWritableBytes) {
    if (minWritableBytes < 0) {
        throw new IllegalArgumentException(String.format(
                "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
    }

    if (minWritableBytes <= writableBytes()) {
        return this;
    }

    if (minWritableBytes > maxCapacity - writerIndex) {
        throw new IndexOutOfBoundsException(String.format(
                "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                writerIndex, minWritableBytes, maxCapacity, this));
    }

    // Normalize the current capacity to the power of 2.
    int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);

    // Adjust to the new capacity.
    capacity(newCapacity);
    return this;
}

代码中首先判断写的长度是否小于0,紧接着判断。当前缓存对象是否有足够的空间存放当前需要写入的最大长度。否则就计算下次需要生产的空间的大小。也就是

代码中的 caculateNewCapacity()方法。


接着 我们可继续看看它的计算空间算法


private int calculateNewCapacity(int minNewCapacity) {
    final int maxCapacity = this.maxCapacity;
    final int threshold = 1048576 * 4; // 4 MiB page

    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);
}

首先判断当前传入的大小是否小于64,否则就返回64,如果大于64且小于threadshould 就每次增大2倍。否则就每次添加4m或者当新需要的空间大于最大空间减去4m时,就直接赋值最大的空间


有了新需要的容器大小,就可以准备扩容了。


public ByteBuf capacity(int newCapacity) {
    ensureAccessible();
    if (newCapacity < 0 || newCapacity > maxCapacity()) {
        throw new IllegalArgumentException("newCapacity: " + newCapacity);
    }

    int oldCapacity = array.length;
    if (newCapacity > oldCapacity) {
        byte[] newArray = new byte[newCapacity];
        System.arraycopy(array, 0, newArray, 0, array.length);
        setArray(newArray);
    } else if (newCapacity < oldCapacity) {
        byte[] newArray = new byte[newCapacity];
        int readerIndex = readerIndex();
        if (readerIndex < newCapacity) {
            int writerIndex = writerIndex();
            if (writerIndex > newCapacity) {
                writerIndex(writerIndex = newCapacity);
            }
            System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
        } else {
            setIndex(newCapacity, newCapacity);
        }
        setArray(newArray);
    }
    return this;
}


首先对引用和参数的校验。然后创建新的

byte[] newArray = new byte[newCapacity]
容器。接着赋值,更新索引。最后返回新的容器

到这里,缓存就成功的扩容了。























   






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值