【JavaNIO的深入研究4】內存映射文件I/O,大文件讀寫操作,Java nio之MappedByteBuffer,高效文件/內存映射
內存映射文件能讓你創建和修改那些因為太大而無法放入內存的文件。有了內存映射文件,你就可以認為文件已經全部讀進了內存,然後把它當成一個非常大的數組來訪問。這種解決辦法能大大簡化修改文件的代碼。
fileChannel.map(FileChannel.MapMode mode, long position, long size)將此通道的文件區域直接映射到內存中。註意,你必須指明,它是從文件的哪個位置開始映射的,映射的範圍又有多大;也就是說,它還可以映射一個大文件的某個小片斷。
MappedByteBuffer是ByteBuffer的子類,因此它具備了ByteBuffer的所有方法,但新添了force()將緩沖區的內容強制刷新到存儲設備中去、load()將存儲設備中的數據加載到內存中、isLoaded()位置內存中的數據是否與存儲設置上同步。這裏只簡單地演示了一下put()和get()方法,除此之外,你還可以使用asCharBuffer( )之類的方法得到相應基本類型數據的緩沖視圖後,可以方便的讀寫基本類型數據。
內存映射文件 I/O 是一種讀和寫文件數據的方法,它可以比常規的基於流或者基於通道的 I/O 快得多。
內存映射文件 I/O 是通過使文件中的數據神奇般地出現為內存數組的內容來完成的。這其初聽起來似乎不過就是將整個文件讀到內存中,但是事實上並不是這樣。一般來說,只有文件中實際讀取或者寫入的部分才會送入(或者 映射 )到內存中。
內存映射並不真的神奇或者多麽不尋常。現代操作系統一般根據需要將文件的部分映射為內存的部分,從而實現文件系統。Java 內存映射機制不過是在底層操作系統中可以采用這種機制時,提供了對該機制的訪問。
盡管創建內存映射文件相當簡單,但是向它寫入可能是危險的。僅只是改變數組的單個元素這樣的簡單操作,就可能會直接修改磁盤上的文件。修改數據與將數據保存到磁盤是沒有分開的。
(1)了解內存映射的最好方法是使用例子。在下面的例子中,我們要將一個 FileChannel
(它的全部或者部分)映射到內存中。為此我們將使用 FileChannel.map()
方法。下面代碼行將文件的前 1024 個字節映射到內存中:
public class UseMappedFile { static private final int start = 0; static private final int size = 1024; static public void main( String args[] ) throws Exception { RandomAccessFile raf = new RandomAccessFile( "usemappedfile.txt", "rw" ); FileChannel fc = raf.getChannel(); MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, start, size ); mbb.put( 0, (byte)97 ); mbb.put( 1023, (byte)122 ); for (int i = start; i < size; i++) { System.out.print((char) mbb.get(i)); } raf.close(); } }
運行結果:
(2)該程序創建了一個128Mb的文件,如果一次性讀到內存可能導致內存溢出,但這裏訪問好像只是一瞬間的事,這是因為,真正調入內存的只是其中的一小部分,其余部分則被放在交換文件上。這樣你就可以很方便地修改超大型的文件了(最大可以到2 GB)。註意,Java是調用操作系統的"文件映射機制"來提升性能的。
public class LargeMappedFiles { static int length = 0x8000000; // 128 Mb public static void main(String[] args) throws Exception { // 為了以可讀可寫的方式打開文件,這裏使用RandomAccessFile來創建文件。 FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel(); //註意,文件通道的可讀可寫要建立在文件流本身可讀寫的基礎之上 MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length); //寫128M的內容 for (int i = 0; i < length; i++) { out.put((byte) ‘x‘); } System.out.println("Finished writing"); //讀取文件中間6個字節內容 for (int i = length / 2; i < length / 2 + 6; i++) { System.out.print((char) out.get(i)); } fc.close(); } }
運行結果:
Finished writing
xxxxxx
(3)java處理大文件,一般用BufferedReader,BufferedInputStream這類帶緩沖的Io類,不過如果文件超大的話,更快的方式是采用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.
【JavaNIO的深入研究4】內存映射文件I/O,大文件讀寫操作,Java nio之MappedByteBuffer,高效文件/內存映射