1. 程式人生 > >Java NIO之緩沖區

Java NIO之緩沖區

打印機 控制器 I/O limit 符號 mat write spa int()

  • 簡介
    • IO概念
      • 緩沖區操作
      • 虛擬內存
      • 文件IO
      • 流IO
  • 緩沖區
    • Buffer屬性
    • Buffer數據填充、翻轉、釋放、壓縮、標記
    • Buffer比較
    • Buffer批量移動
    • 復制緩沖區
  • 字節緩沖區
    • 直接緩沖區
    • 其他緩沖區

簡介

幾個IO事實:

  1. 影響應用程序執行效率的限定因素,往往非處理速率,而是IO
  2. OS要移動大塊數據,往往是在DMA協助下完成,而JVM的IO操作往往是小塊數據,有了NIO,可改變這種情況
  3. JDK1.4,java.nio提供了一套新的抽象用於IO處理

IO概念

緩沖區操作

進程執行IO操作,要麽把緩沖區的數據排幹(read),要麽用數據把緩沖區填滿(write)。
進程使用read()系統調用,要求其緩沖區需要被填滿,內核隨機向磁盤控制硬件發出命令,通過DMA(無需主CPU協助)講數據寫入內核內存緩沖區,一旦磁盤控制器把緩沖區裝滿,內核即把數據從內核空間的臨時緩沖區拷貝到進程read()調用時執行的緩沖區。

技術分享圖片

其中用戶空間和內核空間
用戶空間是常規進程所在區域,如JVM。內核空間是操作系統所在區域,有特別權力,能與設備控制器通訊,控制用戶區域進程的運行狀態等。總之,所有IO都直接或間接通過內核空間。
硬件不能直接訪問用戶空間,硬件設備操作的是固定大小的數據塊,而用戶進程請求的可能是任意大小的或非對齊的數據塊。

虛擬內存

使用虛擬地址取代物理內存地址,有個兩大類好處,一是多個虛擬地址可知指向同一物理地址,二是虛擬內存空間可大於硬件內存。

技術分享圖片

文件IO

文件系統與磁盤不同,文件系統是高層次的抽象,是安排、解釋磁盤或其他隨機存取塊設備數據的一種獨特方法。
文件系統數據也會同其他內存頁一樣得到高速緩存,對於隨後發生的IO請求,文件數據的部分或全部可能仍位於物理內存中,無需從磁盤讀取。

文件鎖定:分為共享和獨占。多個共享鎖可同時對同一文件區域發生作用,獨占鎖要求相關區域不能有其他鎖起作用。

流IO

原理模仿通道,必須順序存取,如控制臺設備、打印機端口。

緩沖區

Buffer類是nio構造基礎,一個Buffer對象是固定數量的數據容器,在這裏的數據可悲存儲並在之後用於檢索。可以理解為是I/O操作中數據的中轉站。緩沖區直接為通道(Channel)服務,寫入數據到通道或從通道讀取數據,這樣的操利用緩沖區數據來傳遞就可以達到對數據高效處理的目的。

Buffer屬性

容量capacity:創建時設定,不可改變。
上界limit:不可讀寫。現存元素的計數。
位置position:下一個要被讀或寫元素的索引。
標記mark:一個備忘位置。

Buffer數據填充、翻轉、釋放、壓縮、標記

java.nio.Buffer類是一個抽象類,不能被實例化。Buffer類的直接子類,如ByteBuffer等也是抽象類,所以也不能被實例化。
但是ByteBuffer類提供了靜態工廠方法來獲得ByteBuffer的實例,終於到了show代碼的時候了=。=,如下:

               //初始化
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數據填充
        buffer.put((byte) ‘H‘).put((byte) ‘e‘).put((byte) ‘l‘).put((byte) ‘l‘).put((byte) ‘o‘);

        System.out.println(buffer.toString());//java.nio.HeapByteBuffer[pos=5 lim=1024 cap=1024]
        System.out.println((char) (buffer.get(0)));//H

        //數據修改
        buffer.put(0, (byte) ‘M‘).put((byte) ‘w‘);

        System.out.println(buffer.toString());//java.nio.HeapByteBuffer[pos=6 lim=1024 cap=1024]
        System.out.println((char) (buffer.get(0)));//M

        //flip將position歸0,將一個待填充狀態的緩沖區翻轉成準備讀出狀態
        System.out.println(buffer);//java.nio.HeapByteBuffer[pos=6 lim=1024 cap=1024]
        buffer.flip();
        //buffer.rewind();//類似flip,但是不影響limit屬性。java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
        System.out.println(buffer);//java.nio.HeapByteBuffer[pos=0 lim=6 cap=1024]

        //使用hasRemaining()判斷是否達到緩沖區上界
        while (buffer.hasRemaining()) {
            System.out.print((char) (buffer.get()));
        }//Mellow
        System.out.println("");

        //壓縮,compact丟棄已釋放數據,保留未釋放數據
        buffer.compact();
        //mark,在某個位置標記,reset( )函數將位置設為當前的標記值。
        // 如果標記值未定義,調 用 reset( )將導致 InvalidMarkException 異常。
        // 一些緩沖區函數會拋棄已經設定的標記 (rewind( ),clear( ),以及 flip( )總是拋棄標記)。
        buffer.position(2).mark();

Buffer比較

比較兩個緩沖區,ByteBuffer已經實現Comparable接口,源碼如下:

 public int compareTo(ByteBuffer that) {
        int n = this.position() + Math.min(this.remaining(), that.remaining());
        for (int i = this.position(), j = that.position(); i < n; i++, j++) {
            int cmp = compare(this.get(i), that.get(j));
            if (cmp != 0)
                return cmp;
        }
        return this.remaining() - that.remaining();
    }

    private static int compare(byte x, byte y) {
        return Byte.compare(x, y);

    }

Buffer批量移動

        //批量取
        byte[] myArray = new byte[1000];
        buffer.get(myArray);
        //等價於:
        buffer.get(myArray, 0, myArray.length);

        //批量存
        byte[] myString = new byte[1000];
        buffer.put(myString);
        //等價於:
        buffer.put(myString,0,myString.length);

復制緩沖區

使用duplicate()函數可以復制緩沖區,會創建一個新的buffer對象,但並不會復制數據,原始緩沖區和副本都會操作同樣的數據元素。

字節緩沖區

所有的基本數據類型都有相應的緩沖區類(布爾除外),字節類型比較特殊,字節是操作系統和其IO設備使用的基本類型。
非字節類型的基本類型,也是由字節組合成的,比如char2個字節,int4個字節,double8個字節。組合的字節是有順序的,Java默認的字節順序是大端字節順序,參見類ByteOreder。

直接緩沖區

字節緩沖區可以成為通道所執行IO的源頭或目標,非字節緩沖區傳遞給通道,會隱含執行復制內容到一個臨時ByteBuffer,所以直接緩沖區是IO的最佳選擇。
通過isDirect()函數,判定緩沖區是否為直接緩沖區。

其他緩沖區

視圖緩沖區,這種視圖對象維持它自己的屬性、容量、位置、上界和標記,但是和原來緩沖區共享數據元素。
CharBuffer charBuffer = byteBuffer.asCharBuffer( );

ByteBuffer類為每一種原始數據類型提供了存取和轉化的方法,比如getInt()函數被調用,從當前位置開始的4個字節會被包裝成一個int類型變量返回。
對於無符號數據,自行實現,註意精度問題。

Java NIO之緩沖區