java nio 之MappedByteBuffer,高效檔案/記憶體對映
阿新 • • 發佈:2019-02-12
MappedByteBuffer是java nio引入的檔案記憶體對映方案,讀寫效能極高。NIO最主要的就是實現了對非同步操作的支援。其中一種通過把一個套接字通道(SocketChannel)註冊到一個選擇器(Selector)中,不時呼叫後者的選擇(select)方法就能返回滿足的選擇鍵(SelectionKey),鍵中包含了SOCKET事件資訊。這就是select模型。
SocketChannel的讀寫是通過一個類叫ByteBuffer(java.nio.ByteBuffer)來操作的.這個類本身的設計是不錯的,比直接操作byte[]方便多了. ByteBuffer有兩種模式:直接/間接.間接模式最典型(也只有這麼一種)的就是HeapByteBuffer,即操作堆記憶體 (byte[]).但是記憶體畢竟有限,如果我要傳送一個1G的檔案怎麼辦?不可能真的去分配1G的記憶體.這時就必須使用"直接"模式,即 MappedByteBuffer,檔案對映.
先中斷一下,談談作業系統的記憶體管理.一般作業系統的記憶體分兩部分:實體記憶體;虛擬記憶體.虛擬記憶體一般使用的是頁面映像檔案,即硬碟中的某個(某些)特殊的檔案.作業系統負責頁面檔案內容的讀寫,這個過程叫"頁面中斷/切換". MappedByteBuffer也是類似的,你可以把整個檔案(不管檔案有多大)看成是一個ByteBuffer.MappedByteBuffer 只是一種特殊的 ByteBuffer ,即是ByteBuffer的子類。 MappedByteBuffer 將檔案直接對映到記憶體(這裡的記憶體指的是虛擬記憶體,並不是實體記憶體)。通常,可以對映整個檔案,如果檔案比較大的話可以分段進行對映,只要指定檔案的那個部分就可以。
三種方式:
FileChannel提供了map方法來把檔案影射為記憶體映像檔案: MappedByteBuffer map(int mode,long position,long size); 可以把檔案的從position開始的size大小的區域對映為記憶體映像檔案,mode指出了 可訪問該記憶體映像檔案的方式:READ_ONLY,READ_WRITE,PRIVATE.
a. READ_ONLY,(只讀): 試圖修改得到的緩衝區將導致丟擲 ReadOnlyBufferException.(MapMode.READ_ONLY)
b. READ_WRITE(讀/寫): 對得到的緩衝區的更改最終將傳播到檔案;該更改對對映到同一檔案的其他程式不一定是可見的。 (MapMode.READ_WRITE)
c. PRIVATE(專用): 對得到的緩衝區的更改不會傳播到檔案,並且該更改對對映到同一檔案的其他程式也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)
三個方法:
a. fore();緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入檔案
b. load()將緩衝區的內容載入記憶體,並返回該緩衝區的引用
c. isLoaded()如果緩衝區的內容在實體記憶體中,則返回真,否則返回假
三個特性:
呼叫通道的map()方法後,即可將檔案的某一部分或全部對映到記憶體中,對映記憶體緩衝區是個直接緩衝區,繼承自ByteBuffer,但相對於ByteBuffer,它有更多的優點:
a. 讀取快
b. 寫入快
c. 隨時隨地寫入
下面來看程式碼:
SocketChannel的讀寫是通過一個類叫ByteBuffer(java.nio.ByteBuffer)來操作的.這個類本身的設計是不錯的,比直接操作byte[]方便多了. ByteBuffer有兩種模式:直接/間接.間接模式最典型(也只有這麼一種)的就是HeapByteBuffer,即操作堆記憶體 (byte[]).但是記憶體畢竟有限,如果我要傳送一個1G的檔案怎麼辦?不可能真的去分配1G的記憶體.這時就必須使用"直接"模式,即 MappedByteBuffer,檔案對映.
先中斷一下,談談作業系統的記憶體管理.一般作業系統的記憶體分兩部分:實體記憶體;虛擬記憶體.虛擬記憶體一般使用的是頁面映像檔案,即硬碟中的某個(某些)特殊的檔案.作業系統負責頁面檔案內容的讀寫,這個過程叫"頁面中斷/切換". MappedByteBuffer也是類似的,你可以把整個檔案(不管檔案有多大)看成是一個ByteBuffer.MappedByteBuffer 只是一種特殊的 ByteBuffer ,即是ByteBuffer的子類。 MappedByteBuffer 將檔案直接對映到記憶體(這裡的記憶體指的是虛擬記憶體,並不是實體記憶體)。通常,可以對映整個檔案,如果檔案比較大的話可以分段進行對映,只要指定檔案的那個部分就可以。
三種方式:
FileChannel提供了map方法來把檔案影射為記憶體映像檔案: MappedByteBuffer map(int mode,long position,long size); 可以把檔案的從position開始的size大小的區域對映為記憶體映像檔案,mode指出了 可訪問該記憶體映像檔案的方式:READ_ONLY,READ_WRITE,PRIVATE.
a. READ_ONLY,(只讀): 試圖修改得到的緩衝區將導致丟擲 ReadOnlyBufferException.(MapMode.READ_ONLY)
b. READ_WRITE(讀/寫): 對得到的緩衝區的更改最終將傳播到檔案;該更改對對映到同一檔案的其他程式不一定是可見的。 (MapMode.READ_WRITE)
c. PRIVATE(專用): 對得到的緩衝區的更改不會傳播到檔案,並且該更改對對映到同一檔案的其他程式也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)
三個方法:
a. fore();緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入檔案
b. load()將緩衝區的內容載入記憶體,並返回該緩衝區的引用
c. isLoaded()如果緩衝區的內容在實體記憶體中,則返回真,否則返回假
三個特性:
呼叫通道的map()方法後,即可將檔案的某一部分或全部對映到記憶體中,對映記憶體緩衝區是個直接緩衝區,繼承自ByteBuffer,但相對於ByteBuffer,它有更多的優點:
a. 讀取快
b. 寫入快
c. 隨時隨地寫入
下面來看程式碼:
package study; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class MapMemeryBuffer { public static void main(String[] args) throws Exception { ByteBuffer byteBuf = ByteBuffer.allocate(1024 * 14 * 1024); byte[] bbb = new byte[14 * 1024 * 1024]; FileInputStream fis = new FileInputStream("e://data/other/UltraEdit_17.00.0.1035_SC.exe"); FileOutputStream fos = new FileOutputStream("e://data/other/outFile.txt"); FileChannel fc = fis.getChannel(); long timeStar = System.currentTimeMillis();// 得到當前的時間 fc.read(byteBuf);// 1 讀取 //MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); System.out.println(fc.size()/1024); long timeEnd = System.currentTimeMillis();// 得到當前的時間 System.out.println("Read time :" + (timeEnd - timeStar) + "ms"); timeStar = System.currentTimeMillis(); fos.write(bbb);//2.寫入 //mbb.flip(); timeEnd = System.currentTimeMillis(); System.out.println("Write time :" + (timeEnd - timeStar) + "ms"); fos.flush(); fc.close(); fis.close(); } } 執行結果: 14235 Read time :24ms Write time :21ms 我們把標註1和2語句註釋掉,換成它們下面的被註釋的那條語句,再來看執行效果。14235 Read time :2ms Write time :0ms
可以看出速度有了很大的提升。MappedByteBuffer的確快,但也存在一些問題,主要就是記憶體佔用和檔案關閉等不確定問題。被MappedByteBuffer開啟的檔案只有在垃圾收集時才會被關閉,而這個點是不確定的。在javadoc裡是這麼說的:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.
這裡提供一種解決方案:
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]); getCleanerMethod.setAccessible(true); sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(byteBuffer, new Object[0]); cleaner.clean(); } catch (Exception e) { e.printStackTrace(); } return null; } });