4、深入剖析Java Nio編程原理之Buffer

章節概覽

Netty源碼分析章節概覽


1、概述

前面我們做了很多基礎知識的準備,包括bio,nio,aio的理論和概念。BIO實現原理和通信原理知識的梳理等。下面我們深入的分析下NIO相關的知識。和Socket類和ServerSocket類相對應的,NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字接口實現。當然這兩種新增的模式支持阻塞模式和非阻塞模式。


2、NIO類庫簡介

NIO在JDK1.4中引入的。主要用來彌補原來同步阻塞I/O的不足。以後的幾個章節中詳細分析:Buffer、Channel、Selector

2.1 Buffer深入分析

Buffer從名字可以很直觀的看出,它是一個緩衝區。它可以緩衝一些需要寫入和讀出的數據。傳統的IO操作面向數據流,意味着每次從流中讀一個或多個字節,直至完成,數據沒有被緩存在任何地方。NIO操作面向緩衝區,數據從Channel讀取到Buffer緩衝區,隨後在Buffer中處理數據。

2.1.1 Buffer入門Demo

入門demo很簡單,就是從文件管道中讀取數據,輸出到控制檯。

public class BufferDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("D:\\gc.log");
        FileChannel channel = fileInputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(20);
        int read = channel.read(buffer);
        while (read != -1) {
            // 在讀取數據之前,需要重置下limit參數,讓limit = position;
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
            // 讀取結束之後,需要重置下參數limit = capacity;
            buffer.clear();
            read = channel.read(buffer);
        }
    }
}
2.1.2、Buffer 構造器和成員變量

在這裏插入圖片描述

public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    // mark 用來臨時存儲當前position的值
    private int mark = -1;
    // 當前數據遊標的位置,在讀寫模式下是不同的:
    // 在寫模式下,每次添加一個字節,position++。
    // 在讀模式下,先把 limit = position,在把position = 0; 這樣每次讀取一個字節position++,直到 position < limit 結束。
    private int position = 0;
    // limit 在讀寫模式下是不同的:
    // 寫模式下,limit表示最多能往Buffer裏寫多少數據,等於capacity值;
    // 讀模式下,limit表示最多可以讀取多少數據,limit = position。
    private int limit;
    // 表示當前緩衝區的容量大小
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    // Buffer 構造函數
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
	......
}
2.1.3、Buffer 類繼承關係說明

在這裏插入圖片描述
Buffer一些常用的類實現。這裏我們主要分析的是ByteBuffer,其他的類關係以此類推。

2.1.3、ByteBuffer 構造函數和成員變量
public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{
    // 在使用堆模式下,創建的hb字節數組,該自己數組由JVM進行管理
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

   // 構造函數
    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;
    }
   	
    // 缺省構造函數,主要爲了滿足於創建非堆字節數組而建立
    ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }

    // 創建堆外數組,通過DirectByteBuffer 實現類進行實現。
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
    // 創建由JVM管理的字節數組。
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
......
}

這裏構造函數採用了缺省方式。其次,創建對象採用的方式是靜態工廠的方式創建對象。

2.1.3、HeapByteBuffer 源碼詳解
class HeapByteBuffer
    extends ByteBuffer
{
    HeapByteBuffer(int cap, int lim) {            // package-private
       super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }

    HeapByteBuffer(byte[] buf, int off, int len) { // package-private

        super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
    }

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

從HeapByteBuffer源碼中可以發現,通過自己創建一個字節數組,或者外部傳入一個字節數組。來構造ByteBuffer對象。該字節數組完全由jvm內存進行管理的。

2.1.3、DirectBuffer 源碼詳解

堆外內存是java直接申請JVM以外的內存。這樣做的好處是減少jvm gc的時間,因爲對象被創建在堆外。其次申請的堆外直接內存,減少數據從堆外內存拷貝到JVM內存的時間。

    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 {
            // 通過Java unsafe 分配堆外內存
            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;
    }

3、ByteBuffer 常用方法

3.1、 mark()

把當前的position賦值給mark

public final Buffer mark() {
        mark = position;
        return this;
    }
3.2、reset()

把mark值還原給position

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
3.3、clear()

一旦讀完Buffer中的數據,需要讓Buffer準備好再次被寫入,clear會恢復狀態值,但不會擦除數據

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
3.4、flip()

Buffer有兩種模式,寫模式和讀模式,flip後Buffer從寫模式變成讀模式

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
3.5、rewind()
public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
3.6、sun.misc.Cleaner#clean 堆外內存清理

堆外內存清理,有兩種方式,一種是JVM進行full gc的時候,會清理堆外內存,這種情況是不可控制的。通常情況下,我們可以自己手動清理堆外內存。以下是部分代碼塊

 public static void clean(final ByteBuffer byteBuffer) {  
        if (byteBuffer.isDirect()) {  
           // 手動釋放清理堆外內存空間
           ((DirectBuffer)byteBuffer).cleaner().clean();  
        }  
  }  

4、 小結

至此Buffer源碼已經分析完成,通過分析可以知道,Buffer內部維護着一個字節數組。字節組數的實現方式分爲兩種,一個是jvm內存中申請,另外一種通過堆外內存申請。同時數據內部維護着幾個位置變量。分別爲:limit,position,capacity,mark。在讀模式和寫模式下不斷的切換,達到緩存數據的目的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章