Java NIO:Buffer缓冲区源码详解以及“零拷贝”

注:NIO源码由机器生成,格式有点乱

option & limit & capacity

/*
     * 功能描述: <br> 分析 option limit capacity的变化
     * 〈〉
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/18 11:19
     */
    public static void testIntBuffer () {

        IntBuffer intBuffer = IntBuffer.allocate(10);
        System.out.println("init buffer:");
        System.out.println("limit:" + intBuffer.limit());
        System.out.println("position:" + intBuffer.position());
        System.out.println("capacity:" + intBuffer.capacity());

        for (int i = 0; i < 5; i++) {
            int randomNumber = new SecureRandom().nextInt(20);
            intBuffer.put(randomNumber);
            System.out.println("put data into buffer:");
            System.out.println("limit:" + intBuffer.limit());
            System.out.println("position:" + intBuffer.position());
            System.out.println("capacity:" + intBuffer.capacity());
        }

        intBuffer.flip();
        System.out.println("after flip():");
        System.out.println("limit:" + intBuffer.limit());
        System.out.println("position:" + intBuffer.position());
        System.out.println("capacity:" + intBuffer.capacity());

        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
        System.out.println("after get():");
        System.out.println("limit:" + intBuffer.limit());
        System.out.println("position:" + intBuffer.position());
        System.out.println("capacity:" + intBuffer.capacity());
    }
  • option:指向下一个将要被 读/写 的元素的位置,0 <= option <= limit
  • limit:指向第一个不能被 读/写 的元素,0 <= limit <= capacity
  • capacity:buffer 包含元素的最大数量,一旦被初始化,不能改变

由 testIntBuffer() 示例可知:

  • 一个 buffer 对象通过 allocate(n) 初始化后,option = 0,limit = n,capacity = n
  • 每次向 buffer 放入元素之后,option将 +1,即向后移动一位,始终指向下一个将要被 读/写 的元素位置。
  • flip() 方法被调用后,buffer转为 读状态,option = 0,limit = 5,始终指向第一个不能被 读/写 的元素。
  • 每次读取元素,option将 +1,即向后移动一位,始终指向下一个将要被 读/写 的元素位置。

allocate(n) 初始化源码如下:

/**
     * Allocates a new int buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  It will have a {@link #array backing array},
     * and its {@link #arrayOffset array offset} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in ints
     *
     * @return  The new int buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static IntBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapIntBuffer(capacity, capacity);
    }
    // For speed these fields are actually declared in X-Buffer;
    // these declarations are here as documentation
    /*

    protected final int[] hb;
    protected final int offset;

    */
    HeapIntBuffer(int cap, int lim) {            // package-private

        super(-1, 0, lim, cap, new int[cap], 0);
        /*
        hb = new int[cap];
        offset = 0;
        */

    }

可以看到返回了一个 HeapIntBuffer 对象,该 HeapIntBuffer 构造方法接收 capacity 和 limit 两个参数,初始化的时候,limit = capacity,并且调用了父类 IntBuffer 的构造方法,从该构造方法可以得出,IntBuffer 底层实质上是一个数组

 

ByteBuffer提供的基本数据类型存放

/*
     * 功能描述: <br> ByteBuffer提供的基本数据类型存放
     * 〈〉
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/18 11:34
     */
    public static void testByteBuffer () {

        ByteBuffer byteBuffer = ByteBuffer.allocate(64);

        byteBuffer.putShort((short) 3213);
        byteBuffer.putInt(1);
        byteBuffer.putLong(112343242312312L);
        byteBuffer.putChar('你');
        byteBuffer.putFloat(12.232f);
        byteBuffer.putDouble(34.131d);

        byteBuffer.flip();

        System.out.println(byteBuffer.getShort());
        System.out.println(byteBuffer.getInt());
        System.out.println(byteBuffer.getLong());
        System.out.println(byteBuffer.getChar());
        System.out.println(byteBuffer.getFloat());
        System.out.println(byteBuffer.getDouble());

    }

slice 切片

/*
     * 功能描述: <br> 验证buffer slice,切片buffer
     * 〈slice产生的新buffer拥有独立的option limit capacity,新的buffer的元素改变会影响源buffer,同样源buffer的改变也会影响新的buffer〉
     *  原因就是sliceBuffer和源buffer共享底层的数组
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/18 13:09
     */
    public static void testBufferSlice () {

        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        for (int i = 0; i < byteBuffer.capacity(); i++) {
            byteBuffer.put((byte) i);
        }

        System.out.println("---------------验证新buffer元素改变对源buffer的影响----------------");

        byteBuffer.position(4);
        byteBuffer.limit(6);

        ByteBuffer sliceBuffer1 = byteBuffer.slice();

        for (int i = 0; i < sliceBuffer1.capacity(); i++) {
            sliceBuffer1.put(i, (byte) (sliceBuffer1.get(i) * 2));
        }


        byteBuffer.clear();//等价于byteBuffer.position(0); byteBuffer.limit(10);

        while (byteBuffer.hasRemaining()) {
            System.out.println(byteBuffer.get());
        }

        System.out.println("---------------验证源buffer元素改变对新buffer的影响----------------");

        byteBuffer.position(4);
        byteBuffer.limit(6);

        ByteBuffer sliceBuffer2 = byteBuffer.slice();

        System.out.println("源buffer改变前:");
        while (sliceBuffer2.hasRemaining()) {
            System.out.println(sliceBuffer2.get());
        }

        byteBuffer.put(4, (byte) 7);
        byteBuffer.put(5, (byte) 8);

        sliceBuffer2 = byteBuffer.slice();


        System.out.println("源buffer改变后:");
        while (sliceBuffer2.hasRemaining()) {
            System.out.println(sliceBuffer2.get());
        }

    }

由示例中 testBufferSlice() 方法运行结果可知:

  • slice() 方法返回一个buffer对象的片段
  • 这个切片 buffer,与源 buffer 共享这个片段的内容
  • 切片 buffer 的改变会影响源 buffer,源buffer 的改变也会影响到切片的初始化

slice() 源码如下:

public ByteBuffer slice() {
        return new HeapByteBuffer(hb,
                                        -1,
                                        0,
                                        this.remaining(),
                                        this.remaining(),
                                        this.position() + offset);
}
/**
     * Returns the number of elements between the current position and the
     * limit.
     *
     * @return  The number of elements remaining in this buffer
     */
    public final int remaining() {
        return limit - position;
    }
protected HeapByteBuffer(byte[] buf,
                                   int mark, int pos, int lim, int cap,
                                   int off)
    {

        super(mark, pos, lim, cap, buf, off);
        /*
        hb = buf;
        offset = off;
        */
    }
// These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    //
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

由源码可以看出:

  • slice() 方法是创建了一个新的 buffer对象,初始化 option = 0,limit = 源buffer limit - 源buffer option,capacity = 源buffer limit - 源buffer option,并设置了 offset 字段作为切片buffer相对于源buffer的偏移量
  • 由于切片 buffer 和源 buffer 实质上操作的是同一个底层数组,所以两者之间可以互相影响

只读buffer

/*
     * 功能描述: <br> 只读buffer
     * 〈〉一个普通的buffer可以调用asReadOnlyBuffer()方法返回一个只读buffer对象。此对象不可逆
     *  任何对只读buffer的内容操作,都会返回一个ReadOnlyBufferException
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/18 14:35
     */
    public static void onlyReadBuffer () {

        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        System.out.println(byteBuffer.getClass());
        ByteBuffer readOnlyBuf = byteBuffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuf.getClass());

    }
protected HeapByteBufferR(byte[] buf,
                                   int mark, int pos, int lim, int cap,
                                   int off)
    {
        super(buf, mark, pos, lim, cap, off);
        this.isReadOnly = true;
    }
public ByteBuffer put(byte x) {
        throw new ReadOnlyBufferException();
    }

由源码可知:

  • asReadOnlyBuffer() 方法返回一个 HeapByteBufferR 类型的buffer对象
  • HeapByteBufferR 类型的buffer对象的 put() 方法直接 throw 一个 ReadOnlyBufferException 异常

零拷贝

/*
     * 功能描述: <br> 测试直接缓冲buffer
     * 〈〉
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/19 19:51
     */
    public static void testDirectBuffer () throws IOException {

        FileInputStream fileInputStream = new FileInputStream("file1.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("file2.txt");

        FileChannel inputChannel = fileInputStream.getChannel();
        FileChannel outputChannel = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(3);


        while (true) {
            byteBuffer.clear();

            int i = inputChannel.read(byteBuffer);

            if (i == -1) {
                break;
            }
            byteBuffer.flip();

            outputChannel.write(byteBuffer);

        }

        inputChannel.close();
        outputChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }

NIO DirectByteBuffer

Java NIO引入了用于通道的缓冲区的ByteBuffer。 ByteBuffer有三个主要的实现:

HeapByteBuffer

在调用ByteBuffer.allocate()时使用。 它被称为堆,因为它保存在JVM的堆空间中,因此你可以获得所有优势,如GC支持和缓存优化。 但是,它不是页面对齐的,这意味着如果你需要通过JNI与本地代码交谈,JVM将不得不复制到对齐的缓冲区空间。

DirectByteBuffer

在调用ByteBuffer.allocateDirect()时使用。 JVM将使用malloc()在堆空间之外分配内存空间。 因为它不是由JVM管理的,所以你的内存空间是页面对齐的,不受GC影响,这使得它成为处理本地代码的完美选择。 然而,你要C程序员一样,自己管理这个内存,必须自己分配和释放内存来防止内存泄漏。

说人话,就是 DirectByteBuffer 的操作方法底层都是调用了 native 修饰的方法(和计算机系统交互的方法),对JVM意外的内存空间直接操作,不需要将JVM堆中的独享内存,复制到系统内存中,再进行操作。

这就是为什么很多网络变成的框架底层使用NIO的原因!

// Primary constructor
    //
    DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;



    }

MappedByteBuffer

在调用FileChannel.map()时使用。 与DirectByteBuffer类似,这也是JVM堆外部的情况。 它基本上作为OS mmap()系统调用的包装函数,以便代码直接操作映射的物理内存数据。

猜你喜欢

转载自blog.csdn.net/qq_25805331/article/details/108684558
今日推荐