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

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

option & limit & capacity

/*
     * 功能描述: 
分析 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.
     *
     * 

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 capacity 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提供的基本数据类型存放

/*
     * 功能描述: 
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 切片

/*
     * 功能描述: 
验证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

/*
     * 功能描述: 
只读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 异常

零拷贝

/*
     * 功能描述: 
测试直接缓冲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()系统调用的包装函数,以便代码直接操作映射的物理内存数据。

 

 

 

你可能感兴趣的:(#,Java,IO,NIO,#,Java中常用的必要知识点,java,nio,buffer,零拷贝)