Java NIO 學習筆記一

緩衝區操做

進程執行I/O操做,歸結起來就是向操做系統發出請求,它要麼把緩存區例的數據排幹(寫),要麼用數據把數據區填滿(讀)。進程使用這一機制處理全部數據進出操做。 m7IfQH.png 進程使用read()系統調用,要求其緩存區被填滿。內核隨即向磁盤控制器發出命令,要求其從磁盤讀取數據。經過DMA技術直接將磁盤中的數據寫入內核內存緩存區,一旦磁盤控制器把緩存區填滿,內存當即把數據從內核空間的裏你是緩衝區拷貝到進程執行read()調用時指定的緩衝區。java

發散/匯聚

根據發散/匯聚的概念,進程只須要一個系統調用,就能把一連串的緩衝區地址傳遞給操做系統。而後,內核就能順序填充或排幹多個緩衝區,讀的時候將數據發散到多個用戶空間緩衝區,寫的時候再從多個緩衝區把數據匯聚起來。 m7oYnA.png數組

利用虛擬內存避免一些拷貝

前面提到設備控制器不能使用DMA直接存儲到用戶空間,須要從內核空間拷貝到用戶空間。但在使用內存多重映射技術能夠避免這種拷貝。 現代操做系統都使用虛擬內存,它有極大優勢:緩存

  1. 多個虛擬地址能夠映射到同一個物理地址。
  2. 虛擬內存空間可能大於實際可用的硬件內存。

<!--more-->post

m7T9ud.png 藉助虛擬內存的特色,將內核空間中的緩衝區的虛擬地址和用戶空間的緩衝區虛擬地址映射到一個物理地址(即內存多重映射技術)。 但這也是有前提的,內核與用戶緩衝區必須使用相同的頁對齊,緩衝區的大小還必須是磁盤控制塊大小的倍數。ui

採用分頁技術的操做系統執行IO操做

  1. 肯定請求的數據分佈在文件系統的哪些頁,這些頁不必定都是連續的
  2. 在內核空間種分配足夠的頁,以容納文件系統頁
  3. 在內存頁與磁盤上的文件系統頁之間創建映射
  4. 爲每一個頁產生一個缺頁異常
  5. 虛擬內存系統俘獲缺頁異常,調用相應的缺頁處理程序,將文件系統頁調入主存
  6. 頁面調入成功後,文件系統隊原始數據進行解析,獲取文件內容和屬性信息。

文件系統頁也會和其它內存頁同樣被緩存在主存zthis

內存映射文件

m7sbJ1.png 內存映射IO使用文件系統創建用戶空間直到可用文件系統頁的虛擬內存映射。 當用戶進行觸碰到映射內存空間時,會自動產生頁錯誤,從而將文件系統從磁盤讀進主存。若是用戶修改了映射內存空間時,相應的頁會被標記爲髒頁,隨後就會將更改持久化到磁盤。spa

其優勢:操作系統

  1. 用戶進程直接將文件數據看成內存。
  2. 自動產生頁錯誤,將文件數據從磁盤讀入主存
  3. 操做系統的虛擬內存子系統能夠對這些頁進行智能高速緩存
  4. 數據老是按頁對齊的
  5. 大型文件使用映射能夠節約內存。

文件鎖定機制

文件鎖定機制容許一個進程阻止其它僅從存取某文件,或限制其存取方式。 文件鎖定的鎖定區域能夠是整個文件也可ui細緻到單個字節。線程

共享鎖和獨佔鎖

多個共享鎖能夠同時對同一文件區域發生做用;獨佔鎖要求相關區域不能有其它鎖定在起做用。3d

共享鎖和獨佔鎖的經典應用 --- 控制讀取共享文件的更新 某個進程要讀取文件,就要先取得相關區域的共享鎖。其它但願讀取相同文件區域的進程也會請求共享鎖。多個進程得以並行讀取,互不影響。若是在此時有其它進行想要更新文件,那麼它須要請求獨佔鎖,而後該進行會進入阻滯狀態,直到既有鎖定(共享的,獨佔的)所有解除它才能拿到獨佔鎖。一旦該進程拿到了獨佔鎖,其它全部的共享鎖讀取線程間進入阻滯狀態,直到獨佔鎖解除。

流I/O

並不是全部的I/O都是面向塊的,也有流I/O,其原理模仿了通道。I/O流字節必須順序讀取。流的傳輸通常比塊設備慢,進程用於間歇性輸入。

Buffer類

一個Buffer對象是固定容量的數據的容器。在這裏數據能夠被存儲並在以後用於檢索。

Buffer類的層次圖

m7bFER.png

屬性

private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
  1. 容量(capacity)
  2. 上界(limit) 緩衝區第一個不能被讀或寫的元素。或者說是現存元素的計數。其指明瞭緩衝區中有效內容末端的位置。
  3. 標記(mark) 一個備忘的位置。使用mark()來設定mark=postion.調用reset()設定position=mark 這四個屬性之間遵循的關係爲: 0<= mark <= position <= limit <= capacity

重要方法

put()

put方法就是將元素加入緩衝區,值得注意的是,put方法只改變position,不會改變limit和capacity。 mHPgtU.png

flip()

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

filp()方法將一個可以繼續添加數據的填充狀態的緩衝區翻轉爲一個準備讀出元素的釋放狀態。根據代碼filp()所做的工做不言而喻。

rewind()

rewind方法和flip方法很是相似,可是它不會影響上界屬性,能夠利用rewind方法來從新讀已經被翻轉的緩衝區中的數據。

hasRemaining()

//切換到讀模式
        buffer.flip();
        int count=buffer.remaining();
        System.out.println("當前位置距離上界還有:"+count);
        
        while (buffer.hasRemaining()){
            System.out.print(buffer.get()+" ");
        }

hasRemaining()能夠判斷當前位置是否已經達到buffer的上界。remaining()能夠獲取當前位置與上界的距離。

compact()

當須要從buffer中釋放一部分已經被讀取過的數據時,可使用compact方法,他會將爲讀的數據元素下移動使得第一個元素的索引爲0.該方法在複製數據的場景下,比使用get方法和put方法更加的高效。

關於標記的一些注意點

在未設置標記以前,mark是等於-1的。此時若是調用reset()會拋出InvalidMarkExceptioin異常。值得注意的是由許多方法都是會將mark重置爲-1的。如:rewind(),flip(),clear()等。

緩衝區相等

兩個緩衝區相等的條件:

  1. 對象類型相同
  2. 兩個對象剩餘一樣的元素(剩餘是指position到limit之間的元素)

被認爲相等的兩個緩衝區 mbGwtO.png

批量的移動

以CharBuffer爲例子

public CharBuffer get(char[] dst)
//offset參數是填充dst的起點位置
public CharBuffer get(char[] dst,int offset,int lenth);

public final CharBuffer put(char[] src)
public CharBuffer put(char[] src,int offset,int length)
public CharBUffer put(CharBuffer src)

可使用如下方法高效的讀取處理數據

buffer.flip();
    int[] arr=new int[10];
    while (buffer.hasRemaining()){
        int len=Math.min(arr.length,buffer.remaining());
        buffer.get(arr,0,len);
        //處理數據
        processData(arr,len);
    }

緩存區建立的兩個關鍵方法

public static CharBuffer allocate(int capacity)

public static CharBuffer Wrap(char[] array)
//offset參數用來初始化position參數,length參數用來初始化limit參數
public static CharBuffer wrap(char[] array,int offset,int length)

建立新的緩存區有兩種方式,分別是分配或包裝操做。 allocate方法採用的分配的方式,他會分類一個數組來存儲數據。而wrap方法是將一個數組包裝爲一個緩衝區。這意味着對這個數組的任何改動都會對這個緩衝區可見。

複製緩衝區

public abstract CharBuffer duplicate();
public abstract CharBuffer asReadOnlyBuffer();
public abstract CharBuffer slice();

使用duplicate方法能夠建立一個和原緩衝區共享數據元素的副本緩衝區。它們共享數據元素可是擁有各自的位置,上界和標記屬性。

使用asReadOnlyBuffer方法能夠建立一個只讀的副本緩衝區。它所建立的副本是不容許使用put方法的。

使用slice方法能夠建立一個position到limit的元素的一個副本。

值得注意的是以上三種方法都不會在堆中從新分配空間用來存儲數據。因此它們都是複製緩衝區的方法。

字節緩衝區

字節順序

字節順序分爲大端存儲和小端存儲 大端: 高位存放在內存的低地址位 小端: 低位存放在內存的低地址位

使用ByteOrder order()方法能夠得到該緩衝區的字節順序;使用ByteBuffer order(ByteOrder bo) 方法能夠修改緩衝區的字節順序。

直接緩衝區

字節緩衝區的一大特色就是它能夠是直接緩衝區。它能夠成爲通道所執行的I/O的源頭或目標。

非直接緩衝區:非直接緩衝區將緩衝區創建在JVM內存在中。 mO5GXF.png

非直接緩衝區:直接將緩衝區創建在物理內存中,能夠提升效率。 mO5N79.png

//分配直接緩衝區
        ByteBuffer bf=ByteBuffer.allocateDirect(10);
        //判斷其是不是直接緩衝區,結果是true
        System.out.println(bf.isDirect());

視圖緩衝區

ByteBuffer類容許建立視圖來將byte型緩衝區字節數據映射位其它的原始數據類型。視圖對象維護它本身的容量、位置、上界和標記,可是和原來的緩衝區共享數據元素。

ByteBuffer bf=ByteBuffer.allocate(10);
        IntBuffer intBuffer=bf.asIntBuffer();
相關文章
相關標籤/搜索