JAVA NIO 緩衝區

緩衝區簡介

操作系統有用戶空間與系統空間的概念,JVM對應的JAVA進程是位於用戶空間的,處於該空間的進程不能直接訪問硬件設備,當JAVA進程要進行I/O操作時,只能通過系統調用將控制權交給內核,內核準備好進程所需要的數據,將這些數據拷貝到用戶空間緩衝區(如下圖所示)。

JAVA中將緩衝區抽象爲Buffer類,Buffer類中幾個重要屬性和方法
mark(標記):標記位,初始值爲-1。通過調用mark()方法將mark設置爲position,調用reset()方法將position設置爲mark值,也就是還原到上一次標記的位置。在調用reset()方法時如果mark沒有設置會拋出InvalidMarkException這個異常。
position(位置):表示Buffer中第一個可以被讀取或者寫入的位置,調用put()方法時先調用nextPutIndex()獲取當前位置,然後會將position+1。
limit(限制):表示Buffer中第一個不可讀取或者寫入的位置,調用limit(int)設置limit的值。
capacity(容量):表示Buffer初始大小,一但創建後不可改變。
這幾個屬於有個不變式:0<=mark<=position<=limit<=capacity.
clear():清除緩衝區,重置mark,position設置爲0,limit設置爲capacity;
reset():將position設置爲mark;
flip():將limit設置爲position的位置,position設置爲0.該方法常常與compact()方法配合使用,先看compact()方法,再看它們如何配合的;
compact():這是Buffer的子類擁有的方法,文檔中的解釋是這樣的:壓縮此緩衝區,將緩衝區當前位置和界限之間的數據(如果有)複製到緩衝區的開始處。即將索引 p = position() 處的數據複製到索引0處,將索引 p + 1 處的 int 複製到索引 1 處,依此類推,直到將索引 limit() - 1 處的 int 複製到索引 n = limit() - 1 - p 處。然後將緩衝區的位置設置爲 n+1,並將其界限設置爲其容量。如果已定義了標記,則丟棄它。相當於把position與limit之間的數據整體移動到開始位置,其實是把0到position這段數據擦除掉。
以下是通過緩衝區將字節從一個channel複製到另一個channel的代碼片段:
 buf.clear();         
  while (in.read(buf) >= 0 || buf.position != 0) {
     buf.flip();
     out.write(buf);
     buf.compact();
 }

以上代碼可以看出由於buf容量有限不可能將數據一次性寫入buf,需要分批寫入。寫入時positon有變化,需要將position設置爲0才能讀取,讀取的最大長度其實就是position所在的位置,flip()方法做的就是這個事兒。
但out.write(buf)有可能只寫入了buf中的部分數據,compact做的事就是把這部分沒用的數據覆蓋掉,等待下次將剩餘數據寫入通道。FileChannel的write方法會修改buffer的position值,滿足while循環條件,進入循環體進行下一次讀取。
rewind():重繞緩衝區,將position設置爲0,如果想把一個緩衝區的數據寫入多個目標需要用到此方法。

字節緩衝區

Buffer類是抽象類,它有很多子類: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer.以ByteBuffer來分析緩衝區的實現。

ByteBuffer也是一個抽象類,它有兩種實現:HeapByteBuffer 和 DirectByteBuffer。
HeapByteBuffer對象和普通對象一樣,分配在JVM的堆上,由垃圾回收器回收,在底層維護着一個字節數組,數組長度不可變。
調用allocate方法可以分配一個新的字節緩衝區。 新緩衝區的位置將爲零,其界限將爲其容量,其標記是不確定的。它將具有一個底層實現數組,且其數組偏移量將爲零。除此外還可以通過wrap方法將現有字節數組包裝成一個緩衝區,修改緩衝區內容會同時修改字節數組。
DirectByteBuffer是通過底層的malloc()分配空間,在JVM之外,其實現細節可以參考java.nio.Bits類。當有大量文件需要以內存映射的形式映射到內存中時,首選DirectByteBuffer。

直接緩衝區

字節緩衝區要麼是直接的,要麼是非直接的,最大的區別是於內存位置不同。直接緩衝區可以通過allocateDirect方法來創建,也可以通過內存映射來創建,如果爲直接字節緩衝區JVM會盡最大努力直接在此緩衝區上執行本機 I/O 操作。
非直接緩衝區寫入步驟:
1.創建一個臨時的直接ByteBuffer對象。
2.將非直接緩衝區的內容複製到臨時緩衝中。
3.使用臨時緩衝區執行低層次I/O操作。
4.臨時緩衝區對象離開作用域,並最終成爲被回收的無用數據。
如果採用直接緩衝區會少一次複製過程,如果需要循環使用緩衝區,用直接緩衝區可以很大地提高性能。雖然直接緩衝區使JVM可以進行高效的I/o操作,但它使用的內存是操作系統分配的,繞過了JVM堆棧,建立和銷燬比堆棧上的緩衝區要更大的開銷。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章