1.ByteBuf功能说明

数据传输需要使用缓冲区。

JDK NIO的ByteBuffer有其局限性,缺点如下:

ByteBuffer长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的POJO对象大于ByteBuffer的容量时,会发生索引越界异常;

ByteBuffer只有一个标志位置的指针position,读写的时候需要手工调用flip()和rewind()等,使用者必须小心谨慎的处理这些API,否则很容易导致程序处理失败;

ByteBuffer的API功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。

1.1 ByteBuf的工作原理

ByteBuf是一个Byte数组的缓冲区,它的基本功能应该与JDK的ByteBuffer一致,提供一下几类基本功能。

7种Java基础类型,byte数组,ByteBuffer(ByteBuf)等的读写;

缓冲区自身的copy和slice等;

设置网络字节序;

构造缓冲区实例;

操作位置指针等方法。

由于JDK的ByteBuffer已经提供了这些基础能力的实现。因此Netty ByteBuf的实现可以有两种策略。

参考JDK ByteBuffer的实现,增加额外的功能,解决原ByteBuffer的缺点;

聚合JDK ByteBuffer,通过Facade模式对其进行包装,可以减少自身代码量,降低实现成本。

java 组装byte java bytebuf_java 组装byte

java 组装byte java bytebuf_ci_02

ByteBuf通过两个位置指针来协助缓冲区的读写操作,读操作使用readerIndex,写操作使用writerIndex.

readerIndex和writerIndex的取值一开始都是0,随着数据的写入writerIndex会增加,读取数据会使readerIndex增加,但是他不会超过writerIndex。在读取之后,0~readerIndex就被视为discard的,调用discardReadBytes方法,可以释放这部分空间,它的作用类似ByteBuffer的compact方法。ReaderIndex和writerIndex之间的数据是可读取的,等价于ByteBuffer position 和limit 之间的数据。writerIndex和capacity之间的空间是可写的等价于ByteBuffer limit和capacity之间的可用空间。

由于写操作不修改readerIndex指针,读操作不修改writerIndex指针,因此读写之间不再需要调整位置指针,这极大的简化了缓冲区的读写操作,避免了由于遗漏或者不熟悉flip()操作导致的功能异常。

java 组装byte java bytebuf_netty_03

写入N个字节之后的ByteBuf如图

java 组装byte java bytebuf_netty_04

下面我们继续分析ByteBuf是如何实现动态扩展的。

java 组装byte java bytebuf_ide_05

java 组装byte java bytebuf_ide_06

java 组装byte java bytebuf_ide_07

java 组装byte java bytebuf_java 组装byte_08

java 组装byte java bytebuf_ci_09

转换成标准的ByteBuffer

java 组装byte java bytebuf_netty_10

2.ByteBuf源码分析

  

java 组装byte java bytebuf_java 组装byte_11

堆内存:(HeapByteBuf)字节缓冲区:特点是内存的分配和回收速度快,可以被JVM自动回收;缺点是如果进行Socket的I?O读写,需要额外做一次内存复制,将堆内存对应的缓冲区复制到内核Channel中,性能会有一定程度的下降。

直接内存:(DirectByteBuf)字节缓冲区:非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度慢一些,但是将它写入或者从Socket Channel中读取时,由于少了一次内存复制,速度比堆内存快。

java 组装byte java bytebuf_ci_12

java 组装byte java bytebuf_netty_13

2.1主要成员变量

public abstract class AbstractByteBuf extends ByteBuf {

    static final ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(ByteBuf.class);

    int readerIndex;
    int writerIndex;
    private int markedReaderIndex;
    private int markedWriterIndex;

    private int maxCapacity;

    private SwappedByteBuf swappedBuf;

2.2读操作簇

举例:

getBytes方法,从当前的读索引开始,复制length个字节到目标byte数组中。

@Override
    public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }
/**
     * Throws an {@link IndexOutOfBoundsException} if the current
     * {@linkplain #readableBytes() readable bytes} of this buffer is less
     * than the specified value.
     */
    protected final void checkReadableBytes(int minimumReadableBytes) {
        ensureAccessible();
        if (minimumReadableBytes < 0) {
            throw new IllegalArgumentException("minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)");
        }
        if (readerIndex > writerIndex - minimumReadableBytes) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                    readerIndex, minimumReadableBytes, writerIndex, this));
        }
    }
/**
     * Should be called by every method that tries to access the buffers content to check
     * if the buffer was released before.
     */
    protected final void ensureAccessible() {
        if (refCnt() == 0) {
            throw new IllegalReferenceCountException(0);
        }
    }

2.3写操作簇

writeBytes(byte[] src,int srcIndex,int length),它的功能是将源字节数组从srcIndex开始,到srcIndex+length截止的字节数组写入到当前的ByteBuf中。

@Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureAccessible();
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }
@Override
    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 = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);

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

java 组装byte java bytebuf_java 组装byte_14

java 组装byte java bytebuf_java_15

java 组装byte java bytebuf_netty_16

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

java 组装byte java bytebuf_java_17

2.4操作索引

@Override
    public ByteBuf readerIndex(int readerIndex) {
        if (readerIndex < 0 || readerIndex > writerIndex) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))", readerIndex, writerIndex));
        }
        this.readerIndex = readerIndex;
        return this;
    }

2.5重用缓冲区

java 组装byte java bytebuf_netty_18

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

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

java 组装byte java bytebuf_netty_19

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

6.skipBytes

java 组装byte java bytebuf_netty_20

@Override
    public ByteBuf skipBytes(int length) {
        checkReadableBytes(length);
        readerIndex += length;
        return this;
    }

java 组装byte java bytebuf_ide_21

备注:文章参考《netty权威指南》,作者:李林锋。