Netty zero copy之CompositeByteBuf读取和写入

一、 测试代码:

    public static void main(String[] args) {

        ByteBuf buffer = Unpooled.buffer(7);
        ByteBuf buffer1 = Unpooled.buffer(10);

        buffer.writeBytes("version".getBytes());
        buffer1.writeBytes("version".getBytes());

        ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer, buffer1);

        byteBuf.writeBytes("src".getBytes());
        byteBuf.writeBytes("src".getBytes());
        System.out.println(byteBuf.readerIndex());
        System.out.println(byteBuf.writerIndex());
    }

从 wrapperBuffer开始进入调试:ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer, buffer1);
这个函数有两个参数,第一个参数是决定最大组件的数量,第二个可变参数buffers的数量。
首先根据buffers的数量进行,这里因为我们是两个buffer,会进入default

  public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) {
        switch (buffers.length) {
        case 0:
            break;
        case 1:
            ByteBuf buffer = buffers[0];
            if (buffer.isReadable()) {
                return wrappedBuffer(buffer.order(BIG_ENDIAN));
            } else {
                buffer.release();
            }
            break;
        default:
            for (int i = 0; i < buffers.length; i++) {
                ByteBuf buf = buffers[i];
                if (buf.isReadable()) {
                    **return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers, i, buffers.length);**
                }
                buf.release();
            }
            break;
        }
        return EMPTY_BUFFER;
    }

首先进入CompositeByteBuf的构造函数:

 CompositeByteBuf(
            ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf[] buffers, int offset, int len) {
        super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY);
        if (alloc == null) {
            throw new NullPointerException("alloc");
        }
        if (maxNumComponents < 2) {
            throw new IllegalArgumentException(
                    "maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
        }

        this.alloc = alloc;
        this.direct = direct;
        this.maxNumComponents = maxNumComponents;
        components = newList(maxNumComponents);

        **addComponents0(false, 0, buffers, offset, len);**
        consolidateIfNeeded();
        setIndex(0, capacity());
    }

进入addComponents0函数,其中Component是CompositeByteBuf的一个私有内部类,只能在该类内部使用和获取。
一个Component会持有一个buffer。CompositeByteBuf实际上是一个Component的集合,并通过读写来动态的维护ridx和widx。

 private static final class Component {
        final ByteBuf buf;
        // buffer长度 
        final int length;
        // 这个字段标识着该Component索引之前(不包含自身)的所有Component的endOffset偏移量之和
        int offset;
        // 这个字段标识着该Component索引之前(连同他自身)的所有Component的endOffset偏移量之和,
        // 也就是到该buffer为止的所有buffer的readableBytes之和
        int endOffset;

        Component(ByteBuf buf) {
            this.buf = buf;
            length = buf.readableBytes();
        }

        void freeIfNecessary() {
            buf.release(); // We should not get a NPE here. If so, it must be a bug.
        }
    }

接下来进入addComponents方法,这里就是一个while循环,同时进行一些动态校验:

 private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteBuf[] buffers, int offset, int len) {
        checkNotNull(buffers, "buffers");
        int i = offset;
        try {
            checkComponentIndex(cIndex);

            // No need for consolidation
            while (i < len) {
                // Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0
                // will release if an exception occurs, and we also release in the finally block here).
                ByteBuf b = buffers[i++];
                if (b == null) {
                    break;
                }
                ***cIndex = addComponent0(increaseWriterIndex, cIndex, b) + 1;***
                int size = components.size();
                if (cIndex > size) {
                    cIndex = size;
                }
            }
            return cIndex;
        } finally {
            for (; i < len; ++i) {
                ByteBuf b = buffers[i];
                if (b != null) {
                    try {
                        b.release();
                    } catch (Throwable ignored) {
                        // ignore
                    }
                }
            }
        }
    }

然后进入最终的核心方法:addComponent0:

private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
        assert buffer != null;
        boolean wasAdded = false;
        try {
            checkComponentIndex(cIndex);

            int readableBytes = buffer.readableBytes();

            // No need to consolidate - just add a component to the list.
            @SuppressWarnings("deprecation")
            // buffer封装Component
            Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
            if (cIndex == components.size()) {
                wasAdded = components.add(c);
                if (cIndex == 0) {
                    // 如果是第一个组件,就将它的readableBytes赋值给endOffset。
                    c.endOffset = readableBytes;
                } else {
                    // 维护偏移量
                    // 获取前一个组件,然后将它的endOffset赋值给当前组件的offset
                    Component prev = components.get(cIndex - 1);
                    c.offset = prev.endOffset;
                    c.endOffset = c.offset + readableBytes;
                }
            } else {
                components.add(cIndex, c);
                wasAdded = true;
                if (readableBytes != 0) {
                    updateComponentOffsets(cIndex);
                }
            }
            if (increaseWriterIndex) {
                // 这里维护组合buffer的读索引
                writerIndex(writerIndex() + buffer.readableBytes());
            }
            return cIndex;
        } finally {
            if (!wasAdded) {
                buffer.release();
            }
        }
    }

这里我们看到,netty在构建Composite的Component组合时,同时在它的内部维护了两个偏移量。
offset和endOffset。
我们知道,CompositeByteBuf只是一个逻辑上的组合buffer,内部实际上是多个独立的buffer。
因此,组合buffer的readableBytes必然是所有buffer的所有可读字节之和。
因此,每当读完一个buffer之后,就必然要取下个offset与前一个endOffset相同的buffer去读取数据,而不是在当前readableBytes==0的buffer内继续读取。


接下来看一下,读取和写入:
读取:

@Override
    public CompositeByteBuf getBytes(int index, byte[] dst) {
        return (CompositeByteBuf) super.getBytes(index, dst);
    }
 @Override
    public ByteBuf getBytes(int index, byte[] dst) {
        getBytes(index, dst, 0, dst.length);
        return this;
    }
    @Override
    public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
        checkDstIndex(index, length, dstIndex, dst.length);
        if (length == 0) {
            return this;
        }

        **// 先把读取的索引位置转换成component内的索引位置
        int i = toComponentIndex(index);**
        while (length > 0) {
            // 拿到index所处的component
            Component c = components.get(i);
            // 获取component中的buffer
            ByteBuf s = c.buf;
            // 偏移量
            int adjustment = c.offset;
            // 获取需要在当前buffer读取的字节数
            int localLength = Math.min(length, s.capacity() - (index - adjustment));
            // 读取
            s.getBytes(index - adjustment, dst, dstIndex, localLength);
            // 维护读写索引位置
            index += localLength;
            dstIndex += localLength;
            // 剩余需要读取的长度
            length -= localLength;
            i ++;
        }
        return this;
    }
   /**
     * 返回给定偏移量的索引位置
     * Return the index for the given offset
     */
    public int toComponentIndex(int offset) {
        checkIndex(offset);

        for (int low = 0, high = components.size(); low <= high;) {
            // 查找index所处的位置,即 c.offset <= offset < c.endOffset
            int mid = low + high >>> 1;
            Component c = components.get(mid);
            if (offset >= c.endOffset) {
                low = mid + 1;
            } else if (offset < c.offset) {
                high = mid - 1;
            } else {
                return mid;
            }
        }

        throw new Error("should not reach here");
    }

CompositeByteBuf的读取是比较简单的。
getBytes的时候需要入参读取的开始位置,然后根据这个其实位置来判断将要读取的component的位置,
也就是通过 查找index所处的位置,即 c.offset <= offset < c.endOffset
进而获取component的索引位置,然后通过维护索引位置以及需要读取的长度length,循环的读取component。

接下来看写操作:
我们说CompositeByteBuf是用来把多个buffer组合成一个逻辑buffer。是netty zero-copy的关键。
那么这里netty是如何把需要新的内容写入组合buffer中呢?
实际上,netty是在所有的buffer之外,新增加了一个Component,同事利用这个component进行读写,这样
就能保证不会影响原来的buffer,并且不需要内存复制就完成了向组合缓冲区中写入数据。
源码:

 @Override
    public CompositeByteBuf writeBytes(byte[] src) {
        return (CompositeByteBuf) super.writeBytes(src);
    }
 @Override
    public ByteBuf writeBytes(byte[] src) {
        writeBytes(src, 0, src.length);
        return this;
    }
@Override
    public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        return (CompositeByteBuf) super.writeBytes(src, srcIndex, length);
    }
  @Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureAccessible();
        // 动态扩容
        **ensureWritable(length);**
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }
 // 动态扩容:以64byte为初始容量进行倍增,达到4MB以后,以4MB为步幅进行步进
 // 这么做的原因是为了在初始容量不大的时候,可以很快的达到所需要的容量,同时在容量小的时候,倍增并不会浪费更多的内存
 // 但是容量增加到足够大的时候,再进行倍增就很容易产生内存浪费诶。这里的4MB是一个经验值。
 // CompositeByteBuf在这里面进行了重组,它重写了capacity()方法,并且在这里面padding了一个组件
 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));
        }

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

        // Adjust to the new capacity.
        capacity(newCapacity);
    @Override
    public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
        if (minNewCapacity < 0) {
            throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expected: 0+)");
        }
        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 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.
        // 以64byte为基数进行倍增
        int newCapacity = 64;
        while (newCapacity < minNewCapacity) {
            newCapacity <<= 1;
        }

        return Math.min(newCapacity, maxCapacity);
    }

CompositeByteBuf重写了capacity这个计算新容量的方法:
这里netty重新分配了一个buffer,并且将它padding到了Composite的Component中,并且重新维护了CompositeByteBuf的读写索引。
这个新分配的ByteBuf的容量是重新计算的容量减去现有CompositeByteBuf的容量

这里有一个疑问。使用CompositeByteBuf进行写入之后,第三个padding的bytebuf的读写索引会变得很奇怪,有兴趣的同学可以观察一下。

@Override
    public CompositeByteBuf capacity(int newCapacity) {
        checkNewCapacity(newCapacity);

        int oldCapacity = capacity();
        if (newCapacity > oldCapacity) {
            final int paddingLength = newCapacity - oldCapacity;
            ByteBuf padding;
            int nComponents = components.size();
            if (nComponents < maxNumComponents) {
                // 重新获取buffer
                padding = allocBuffer(paddingLength);
                padding.setIndex(0, paddingLength);
                // 新增组件
                addComponent0(false, components.size(), padding);
            } else {
                padding = allocBuffer(paddingLength);
                padding.setIndex(0, paddingLength);
                // FIXME: No need to create a padding buffer and consolidate.
                // Just create a big single buffer and put the current content there.
                addComponent0(false, components.size(), padding);
                consolidateIfNeeded();
            }
        } else if (newCapacity < oldCapacity) {
            int bytesToTrim = oldCapacity - newCapacity;
            for (ListIterator<Component> i = components.listIterator(components.size()); i.hasPrevious();) {
                Component c = i.previous();
                if (bytesToTrim >= c.length) {
                    bytesToTrim -= c.length;
                    i.remove();
                    continue;
                }

                // Replace the last component with the trimmed slice.
                Component newC = new Component(c.buf.slice(0, c.length - bytesToTrim));
                newC.offset = c.offset;
                newC.endOffset = newC.offset + newC.length;
                i.set(newC);
                break;
            }

            if (readerIndex() > newCapacity) {
                setIndex(newCapacity, newCapacity);
            } else if (writerIndex() > newCapacity) {
                writerIndex(newCapacity);
            }
        }
        return this;
    }

总结:
到这里CompositeByteBuf的核心代码基本上就完成了。通过源码可以大致看出,CompositeByteBuf实质上是把多个ByteBuf组合成了一个逻辑上的buffer。
同时各个buffer之间独立维护自己的读写索引。而外层的CompositeByteBuf自己会单独维护一个读写索引。
读取的时候,就是根据入参的读取位置,查找相应的component,然后开始顺序读取各个buffer中的可读字节。
新增的时候,就会根据动态扩容的结果,向现有的compoennt中padding一个新的buffer,利用新的buffer进行读写,从而不影响原有buffer。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值