Java NIO 之 Buffer Java NIO (Non Blocking IO 或者 New IO)是一種非阻塞IO的實現。NIO通過Channel、Buffer、Selector幾個組件的協同實現提升IO效率的目的。而ByteBuffer是其中最基礎的一種Buffer實現。 阻塞 or 非 ...
Java NIO 之 Buffer
Java NIO (Non Blocking IO 或者 New IO)是一種非阻塞IO的實現。NIO通過Channel、Buffer、Selector幾個組件的協同實現提升IO效率的目的。而ByteBuffer是其中最基礎的一種Buffer實現。
阻塞 or 非阻塞
阻塞/非阻塞,同步/非同步是兩組非常容易產生混淆的概念。
同步/非同步:是從消息通信機制的劃分,如果調用者在沒有得到結果前“調用”不返回,則是同步的。而如果調用發出後,調用直接返回,結果由被調用者通過某種機制(通知、狀態)返回,則是非同步的。
阻塞/非阻塞:是站在調用者的角度,描述調用者在等待調用結果時的狀態。阻塞是指,在得到返回結果前,當前線程會被一直掛起,只有在得到結果後才返回。非阻塞是指,調用不會阻塞當前線程,可能不會得到想要的結果,但會立刻返回。
以找老闆簽報銷單為例:
如果去老闆工位發現老闆不在,你一直站在旁邊等老闆回來簽字才回去,那你就是阻塞的(好蠢。。。);
如果去老闆工位發現老闆不在,你立馬回來繼續擼代碼,過會又跑去看看老闆在不在,如果不在又回來喝口水。。。那這個過程你就是非阻塞的(也有點蠢。。。);
而上述兩個過程中,“通信機制”都是同步的。而:
如果,你跑過去找老闆簽字,老闆說放這吧,過會老闆把簽好的報銷單交給你,那這次交互過程就是非同步的(想得美。。。);
可見JAVA NIO是非阻塞式的IO,是同步的IO機制。
Buffer的結構
Buffer通過position,limit,capacity三個變數管理內容。其中:capacity標記Buffer總容量大小;position標識當前可讀或者可寫的初始位置;limit標記當前可讀或者可寫的極限位置,當Buffer處於write模式時,limit=capacity,當切換至Read模式時,limit為對應寫模式時的position。三者滿足:position <= limit <= capacity;一種Buffer從寫模式切換至讀模式的示意如下圖:
Flip,Clear與Rewind
Clear操作,置position=0, limit=capacity,將Buffer置於寫模式;
Flip操作,置limit=last_position, position=0,將Buffer置於讀模式;
Rewind操作,置position=0, limit不變,使得可以重新讀取Buffer中的內容。
HeapByteBuffer , Direct ByteBuffer 與 MappedByteBuffer
ByteBuffer本質是一塊記憶體區域。對於ByteBuffer,可以通過allocate(int)和allocateDirect(int)分別分配Heap和Direct Buffer。ByteBuffer持有僅對Heap Buffer有效的一個位元組型數組:
final byte[] hb; // Non-null only for heap buffers
可見HeapByteBuffer是直接分配在堆上的,可以簡單理解為byte[]的一種封裝。
而Direct Buffer不直接分配在堆上,其不受GC管理(而指向這塊記憶體的Java對象是受GC管理的,只有GC回收了這個對象,操作系統才會釋放Direct Buffer的記憶體空間)。
由於Direct Buffer由系統直接管理,其讀寫的消耗小於在堆上進行讀寫(減少了從系統至程式記憶體空間的拷貝)。實際上每次使用ByteBuffer進行讀寫時,都會臨時開闢一段DirectBuffer(JDK實現上對其做了池化,避免了頻繁創建和釋放DirectBuffer帶來的高系統調用消耗),將ByteBuffer中的內容拷貝進其中,再進行後續操作,多了一步Buffer間的拷貝操作。所以對於重覆使用的Buffer,使用Direct Buffer的優點顯而易見。
但是創建和釋放Direct Buffer的代價則高於Heap Buffer(系統函數的直接調用),同時不當地使用DirectBuffer更容易引起記憶體泄漏。
MappedByteBuffer是一種DirectByteBuffer,而其內容是一個文件的全部或者部分映射。由於MappedByteBuffer對進行了文件進行了記憶體映射,避免了讀寫時進行write/read系統調用,所有在大文件讀寫方面具有極高的性能。但是其同樣存在DirectByteBuffer存在的問題,同時涉及文件操作,文件的關閉也依賴於垃圾回收。
對上述三種Buffer進行測試對比,分別讀寫大小為1M,256M和1G的文件(過小的文件並沒有性能上的明顯差異),結果如下:
可見,當文件較小時三者並沒有明顯的性能差異。而在進行大文件的讀寫時MappedByteBuffer表現出了明顯的性能優勢。同時可以看到Direct和Heap之間並沒有明顯的差異。