美文网首页
Netty源码_UnpooledDirectByteBuf详解

Netty源码_UnpooledDirectByteBuf详解

作者: wo883721 | 来源:发表于2021-10-24 11:08 被阅读0次

本篇文章我们讲解缓存区 ByteBuf 八大主要类型中两种,未池化直接缓冲区 UnpooledDirectByteBuf 和 未池化不安全直接缓冲区 UnpooledUnsafeDirectByteBuf

一. UnpooledDirectByteBuf

1.1 介绍

A NIO ByteBuffer based buffer. 
It is recommended to use 
UnpooledByteBufAllocator.directBuffer(int, int),
Unpooled.directBuffer(int) and 
Unpooled.wrappedBuffer(ByteBuffer) 
instead of calling the constructor explicitly.

UnpooledDirectByteBuf 一个基于NIO ByteBuffer的缓冲区。
建议使用UnpooledByteBufAllocator.directBuffer(int, int),Unpooled.directBuffer(int)Unpooled.wrappedBuffer(ByteBuffer);而不是显式调用构造函数。

1.2 成员属性

    private final ByteBufAllocator alloc;

    // accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
    ByteBuffer buffer; 
    private ByteBuffer tmpNioBuf;
    private int capacity;
    private boolean doNotFree;

有四个成员属性:

  1. alloc: 创建此缓存区的ByteBufAllocator 对象。
  2. buffer: NIO 缓存区,用来储存此缓存区的内容数据。
    3.tmpNioBuf: 临时的NIO 缓存区ByteBuffer 对象,其实它是 buffer 的一个 duplicate 对象。
  3. capacity: 此缓存区的当前容量。
  4. doNotFree: 是否不用释放资源。如果这个值为 true,那么最后此缓存区不用释放持有的NIO 缓存区buffer 资源。

1.3 构造方法

    /**
     * 创建一个新的直接缓冲区。
     */
    public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
        super(maxCapacity);
        ObjectUtil.checkNotNull(alloc, "alloc");
        checkPositiveOrZero(initialCapacity, "initialCapacity");
        checkPositiveOrZero(maxCapacity, "maxCapacity");
        if (initialCapacity > maxCapacity) {
            throw new IllegalArgumentException(String.format(
                    "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
        }

        this.alloc = alloc;
        setByteBuffer(allocateDirect(initialCapacity), false);
    }

通过 allocateDirect(initialCapacity) 方法创建一个新的NIO 缓存区实例来初始化此缓存区对象。

    /**
     * 通过包装指定的初始缓冲区来创建一个新的直接缓冲区。
     */
    protected UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
        this(alloc, initialBuffer, maxCapacity, false, true);
    }

    UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer,
            int maxCapacity, boolean doFree, boolean slice) {
        super(maxCapacity);
        ObjectUtil.checkNotNull(alloc, "alloc");
        ObjectUtil.checkNotNull(initialBuffer, "initialBuffer");
        if (!initialBuffer.isDirect()) {
            throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
        }
        if (initialBuffer.isReadOnly()) {
            throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
        }

        int initialCapacity = initialBuffer.remaining();
        if (initialCapacity > maxCapacity) {
            throw new IllegalArgumentException(String.format(
                    "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
        }

        this.alloc = alloc;
        doNotFree = !doFree;
        setByteBuffer((slice ? initialBuffer.slice() : initialBuffer).order(ByteOrder.BIG_ENDIAN), false);
        writerIndex(initialCapacity);
    }

利用现有的NIO 缓存区创建此缓存区。

1.4 重要方法

1.4.1 创建,释放和替换 NIO 缓存区

  1. 创建 NIO 缓存区
     protected ByteBuffer allocateDirect(int initialCapacity) {
         return ByteBuffer.allocateDirect(initialCapacity);
     }
    
    通过 ByteBufferallocateDirect 方法,创建一个NIO 缓存区DirectByteBuffer 对象。
  2. 释放NIO 缓存区
     protected void freeDirect(ByteBuffer buffer) {
         PlatformDependent.freeDirectBuffer(buffer);
     }
    
    调用 PlatformDependent 的方法来释放DirectByteBuffer 对象。
  3. 替换 NIO 缓存区
     void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
         // 是否需要释放老的 NIO 缓存区
         if (tryFree) {
             ByteBuffer oldBuffer = this.buffer;
             if (oldBuffer != null) {
                 if (doNotFree) {
                     doNotFree = false;
                 } else {
                     // 释放老的 NIO 缓存区
                     freeDirect(oldBuffer);
                 }
             }
         }
         // 替换此缓存区拥有的 buffer
         this.buffer = buffer;
         tmpNioBuf = null;
         // 得到当前缓存区容量
         capacity = buffer.remaining();
     }
    

1.4.2 设置缓存区容量

    @Override
    public int capacity() {
        return capacity;
    }

    // 在容量减少后调用
    protected final void trimIndicesToCapacity(int newCapacity) {
        // 写索引比 新容量大,那么才需要改变读写索引的值
        if (writerIndex() > newCapacity) {
            // 写索引的值就是新容量newCapacity,
            // 而读索引的值是读索引和新容量newCapacity之间的较小值
            setIndex0(Math.min(readerIndex(), newCapacity), newCapacity);
        }
    }

    @Override
    public ByteBuf capacity(int newCapacity) {
        // 检查新容量是否越界
        checkNewCapacity(newCapacity);
        // 老的缓存区容量
        int oldCapacity = capacity;
        // 新老容量相等,那么不用改变,直接返回
        if (newCapacity == oldCapacity) {
            return this;
        }
        // 需要复制的内容字节数
        int bytesToCopy;
        if (newCapacity > oldCapacity) {
            // 新容量比老容量大,即扩大缓存区,
            // 那么复制的内容字节数就是老容量大小
            bytesToCopy = oldCapacity;
        } else {
            // 新容量比老容量小,就要缩小缓存区
            // 那么读写索引也要改变。
            trimIndicesToCapacity(newCapacity);
            // 需要复制的内容字节数也就是新容量大小
            bytesToCopy = newCapacity;
        }
        ByteBuffer oldBuffer = buffer;
        // 创建新容量的 NIO 缓存区对象
        ByteBuffer newBuffer = allocateDirect(newCapacity);
        oldBuffer.position(0).limit(bytesToCopy);
        newBuffer.position(0).limit(bytesToCopy);
        // 将老缓冲区数据存入新NIO缓存区newBuffer中
        newBuffer.put(oldBuffer).clear();
        // 替换新NIO缓存区newBuffer
        setByteBuffer(newBuffer, true);
        return this;
    }

你会发现当更改缓存区容量时,分为两种情况:

  • 当新容量比老容量大的时候,即扩大缓存区,这时比较简单,只需要将老的缓存区字节数组数据复制到新的缓存区字节数组数据中就可以了。
  • 当新容量比老容量小的时候,即缩小缓存区,这个时候除了数据内容的复制,可能还需要更改读写索引。

1.4.3 get 系列方法

1.4.3.1 获取基本数据类型

    @Override
    public short getShort(int index) {
        ensureAccessible();
        return _getShort(index);
    }

    @Override
    protected short _getShort(int index) {
        return buffer.getShort(index);
    }

    @Override
    protected short _getShortLE(int index) {
        return ByteBufUtil.swapShort(buffer.getShort(index));
    }
     ......

通过 NIO 缓存区buffer 对应方法获取基本数据类型数据。

1.4.3.2 getBytes(int index, ByteBuf dst, int dstIndex, int length)

    @Override
    public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
        // 检查是否越界
        checkDstIndex(index, length, dstIndex, dst.capacity());
        if (dst.hasArray()) {
            // 如果目标缓存区是堆缓存区,那么目标缓存区就有字节数组 dst.array()
            // 调用 getBytes(int, byte[], int, int) 方法,进行字节数组之间的复制
            getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
        } else if (dst.nioBufferCount() > 0) {
            // 如果目标缓存区dst能转成 NIO 缓存区,得到目标缓存区dst对应的 NIO缓存区数组
            // 遍历 NIO缓存区数组, 调用 getBytes(int, ByteBuffer) 方法,
            // 将次缓存区的数据写入到目标缓存区。
            for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
                int bbLen = bb.remaining();
                getBytes(index, bb);
                index += bbLen;
            }
        } else {
            // 以上情况都不符合,调用目标缓存区dst 的 setBytes(int, ByteBuf, int, int)
            // 将此缓存区数据传输到目标缓存区dst 中。
            dst.setBytes(dstIndex, this, index, length);
        }
        return this;
    }

根据目标缓存区dst类型不同,处理的方式也不同。

1.4.3.3 getBytes(int index, byte[] dst, int dstIndex, int length)

   @Override
    public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
        getBytes(index, dst, dstIndex, length, false);
        return this;
    }

    void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
        checkDstIndex(index, length, dstIndex, dst.length);

        // 得到此缓存区对应的 NIO缓存区对象tmpBuf
        ByteBuffer tmpBuf;
        if (internal) {
            tmpBuf = internalNioBuffer();
        } else {
            tmpBuf = buffer.duplicate();
        }
        tmpBuf.clear().position(index).limit(index + length);
        // 通过 ByteBuffer 的get方法,将NIO缓存区tmpBuf的数据读取到 目标字节数组dst
        tmpBuf.get(dst, dstIndex, length);
    }

1.4.3.4 getBytes(int index, ByteBuffer dst)

    @Override
    public ByteBuf getBytes(int index, ByteBuffer dst) {
        getBytes(index, dst, false);
        return this;
    }

    void getBytes(int index, ByteBuffer dst, boolean internal) {
        checkIndex(index, dst.remaining());

        // 得到此缓存区对应的 NIO缓存区对象tmpBuf
        ByteBuffer tmpBuf;
        if (internal) {
            tmpBuf = internalNioBuffer();
        } else {
            tmpBuf = buffer.duplicate();
        }
        tmpBuf.clear().position(index).limit(index + dst.remaining());
        // 调用目标缓存区 dst 的 put 方法,将此缓存区的数据写入到目标缓存区 dst 中。
        dst.put(tmpBuf);
    }

1.4.3.5 和 IO 流的交互

你会发现这些方法都是获取此缓存区对应 NIO 缓存区ByteBuffer对象,调用 ByteBuffer 对象的方法,与 IO 流的交互,进行数据传输

    @Override
    public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
        return getBytes(index, out, length, false);
    }

    private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
        ensureAccessible();
        if (length == 0) {
            return 0;
        }

        ByteBuffer tmpBuf;
        if (internal) {
            tmpBuf = internalNioBuffer();
        } else {
            tmpBuf = buffer.duplicate();
        }
        tmpBuf.clear().position(index).limit(index + length);
        return out.write(tmpBuf);
    }
    @Override
    public int getBytes(int index, FileChannel out, long position, int length) throws IOException {
        return getBytes(index, out, position, length, false);
    }

    private int getBytes(int index, FileChannel out, long position, int length, boolean internal) throws IOException {
        ensureAccessible();
        if (length == 0) {
            return 0;
        }

        ByteBuffer tmpBuf = internal ? internalNioBuffer() : buffer.duplicate();
        tmpBuf.clear().position(index).limit(index + length);
        return out.write(tmpBuf, position);
    }

1.4.4 set 系列方法

get 系列方法一样,set 系列的实现也是靠 NIO 缓存区ByteBuffer对应方法。

1.4.5 剩余方法

剩余方法也几乎都是和 NIO 缓存区ByteBuffer有关,而且也不难,就不做过多介绍了。

1.5 小结

UnpooledDirectByteBuf 主要是通过 NIO 缓存区 buffer 来存储数据。而它获取和设置数据,也都是通过 NIO 缓存区对应方法实现的。

二. UnpooledUnsafeDirectByteBuf

2.1 介绍

A NIO ByteBuffer based buffer. 
It is recommended to use 
UnpooledByteBufAllocator.directBuffer(int, int),
Unpooled.directBuffer(int) and 
Unpooled.wrappedBuffer(ByteBuffer)
 instead of calling the constructor explicitly.}

光看介绍,和 UnpooledDirectByteBuf 没有任何区别。它也是 UnpooledDirectByteBuf 的子类。

那么 UnpooledUnsafeDirectByteBufUnpooledDirectByteBuf 不同处在那里呢?

它们的区别就一点,那就是 UnpooledUnsafeDirectByteBuf 并不是借助 NIO 缓存区的方法来获取和设置数据。
而是通过获取 NIO 缓存区对象的内存地址 memoryAddress,通过 Unsafe 类借助内存地址memoryAddress直接获取或设置数据。

2.2 setByteBuffer(ByteBuffer buffer, boolean tryFree)

    @Override
    final void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
        super.setByteBuffer(buffer, tryFree);
        // 获取 NIO 缓存区buffer 对应的直接内存地址
        memoryAddress = PlatformDependent.directBufferAddress(buffer);
    }

通过复习 setByteBuffer 方法,获取NIO缓存区buffer对应的直接内存地址。

2.3 get 基本数据方法

    // 返回对应的内存地址
    final long addr(int index) {
        return memoryAddress + index;
    }

    @Override
    public byte getByte(int index) {
        checkIndex(index);
        return _getByte(index);
    }

    @Override
    protected byte _getByte(int index) {
        return UnsafeByteBufUtil.getByte(addr(index));
    }

    @Override
    public short getShort(int index) {
        checkIndex(index, 2);
        return _getShort(index);
    }

    @Override
    protected short _getShort(int index) {
        return UnsafeByteBufUtil.getShort(addr(index));
    }
    .... 

通过 UnsafeByteBufUtil 对应方法,直接从内存地址获取对应基本类型数据。

2.4 set 基本数据方法

    @Override
    public ByteBuf setByte(int index, int value) {
        checkIndex(index);
        _setByte(index, value);
        return this;
    }

    @Override
    protected void _setByte(int index, int value) {
        UnsafeByteBufUtil.setByte(addr(index), value);
    }

    @Override
    public ByteBuf setShort(int index, int value) {
        checkIndex(index, 2);
        _setShort(index, value);
        return this;
    }

    @Override
    protected void _setShort(int index, int value) {
        UnsafeByteBufUtil.setShort(addr(index), value);
    }

通过 UnsafeByteBufUtil 对应方法,直接向内存地址设置对应基本类型数据。

2.5 hasMemoryAddress()

    @Override
    public boolean hasMemoryAddress() {
        return true;
    }

    @Override
    public long memoryAddress() {
        ensureAccessible();
        return memoryAddress;
    }

只有这个类型 hasMemoryAddress() 方法才会返回 true

2.6 小结

UnpooledUnsafeDirectByteBuf 就是通过直接从内存地址中获取和设置数据的方式,提高性能。

相关文章

网友评论

      本文标题:Netty源码_UnpooledDirectByteBuf详解

      本文链接:https://www.haomeiwen.com/subject/arqlaltx.html