Java NIO記憶體映射---上G大檔案處理,nio檔案處理
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka
摘要:本文主要講了java中記憶體映射的原理及過程,與傳統IO進行了對比,最後,用執行個體說明了結果。
一、java中的記憶體映射IO和記憶體對應檔是什嗎?
記憶體對應檔非常特別,它允許Java程式直接從記憶體中讀取檔案內容,通過將整個或部分檔案對應到記憶體,由作業系統來處理載入請求和寫入檔案,應用只需要和記憶體打交道,這使得IO操作非常快。載入記憶體對應檔所使用的記憶體在Java堆區之外。Java程式設計語言支援記憶體對應檔,通過java.nio包和MappedByteBuffer 可以從記憶體直接讀寫檔案。
記憶體對應檔
記憶體對應檔,是由一個檔案到一塊記憶體的映射。Win32提供了允許應用程式把檔案對應到一個進程的函數 (CreateFileMapping)。記憶體對應檔與虛擬記憶體有些類似,通過記憶體對應檔可以保留一個地址空間的地區,同時將實體儲存體器提交給此地區,記憶體檔案對應的實體儲存體器來自一個已經存在於磁碟上的檔案,而且在對該檔案進行操作之前必須首先對檔案進行映射。使用記憶體對應檔處理儲存於磁碟上的檔案時,將不必再對檔案執行I/O操作,使得記憶體對應檔在處理大資料量的檔案時能起到相當重要的作用。
記憶體映射IO
在傳統的檔案IO操作中,我們都是叫用作業系統提供的底層標準IO系統調用函數 read()、write() ,此時調用此函數的進程(在JAVA中即java進程)由當前的使用者態切換到核心態,然後OS的核心代碼負責將相應的檔案資料讀取到核心的IO緩衝區,然 後再把資料從核心IO緩衝區拷貝到進程的私人地址空間中去,這樣便完成了一次IO操作。這麼做是為了減少磁碟的IO操作,為了提高效能而考慮的,因為我們的程式訪問一般都帶有局部性,也就是所 謂的局部性原理,在這裡主要是指的空間局部性,即我們訪問了檔案的某一段資料,那麼接下去很可能還會訪問接下去的一段資料,由於磁碟IO操作的速度比直接 訪問記憶體慢了好幾個數量級,所以OS根據局部性原理會在一次 read()系統調用過程中預讀更多的檔案資料緩衝在核心IO緩衝區中,當繼續訪問的檔案資料在緩衝區中時便直接拷貝資料到進程私人空間,避免了再次的低 效率磁碟IO操作。其過程如下
記憶體對應檔和之前說的 標準IO操作最大的不同之處就在於它雖然最終也是要從磁碟讀取資料,但是它並不需要將資料讀取到OS核心緩衝區,而是直接將進程的使用者私人地址空間中的一 部分地區與檔案對象建立起映射關係,就好像直接從記憶體中讀、寫檔案一樣,速度當然快了。
記憶體映射的優缺點
記憶體映射IO最大的優點可能在於效能,這對於建立高頻電子交易系統尤其重要。記憶體對應檔通常比標準通過正常IO訪問檔案要快。另一個巨大的優勢是記憶體映 射IO允許載入不能直接存取的潛在巨大檔案 。經驗表明,記憶體映射IO在大檔案處理方面效能更加優異。儘管它也有不足——增加了分頁錯誤的數目。由於作業系統只將一部分檔案載入到記憶體,如果一個請求 頁面沒有在記憶體中,它將導致分頁錯誤。同樣它可以被用來在兩個進程中共用資料。
支援記憶體映射IO的作業系統
大多數主流作業系統比如Windows平台,UNIX,Solaris和其他類UNIX作業系統都支援記憶體映射IO和64位架構,你幾乎可以將所有檔案對應到記憶體並通過JAVA程式設計語言直接存取。
Java的記憶體映射IO的要點
如下為一些你需要瞭解的java記憶體映射要點:
java通過java.nio包來支援記憶體映射IO。
記憶體對應檔主要用於效能敏感的應用,例如高頻電子交易平台。
通過使用記憶體映射IO,你可以將大檔案載入到記憶體。
記憶體對應檔可能導致頁面請求錯誤,如果請求頁面不在記憶體中的話。
對應檔地區的能力取決于于記憶體定址的大小。在32位機器中,你不能訪問超過4GB或2 ^ 32(以上的檔案)。
記憶體映射IO比起Java中的IO流要快的多。
負載檔案所使用的記憶體是Java堆區之外,並駐留共用記憶體,允許兩個不同進程共用檔案。
記憶體對應檔讀寫由作業系統完成,所以即使在將內容寫入記憶體後java程式崩潰了,它將仍然會將它寫入檔案直到作業系統恢複。
出於效能考慮,推薦使用直接位元組緩衝而不是非直接緩衝。
不要頻繁調用MappedByteBuffer.force()方法,這個方法意味著強制作業系統將記憶體中的內容寫入磁碟,所以如果你每次寫入記憶體對應檔都調用force()方法,你將不會體會到使用映射位元組緩衝的好處,相反,它(的效能)將類似於磁碟IO的效能。
萬一發生了電源故障或主機故障,將會有很小的機率發生記憶體對應檔沒有寫入到磁碟,這意味著你可能會丟失關鍵資料。
二、執行個體代碼
1、傳統IO讀取資料,不指定緩衝區大小
/** * 傳統IO讀取資料,不指定緩衝區大小 * @author linbingwen * @since 2015年9月5日 * @param path * @return */ public static void readFile1(String path) { long start = System.currentTimeMillis();//開始時間 File file = new File(path); if (file.isFile()) { BufferedReader bufferedReader = null; FileReader fileReader = null; try { fileReader = new FileReader(file); bufferedReader = new BufferedReader(fileReader); String line = bufferedReader.readLine(); System.out.println("========================== 傳統IO讀取資料,使用虛擬機器堆記憶體 =========================="); while (line != null) { //按行讀資料 System.out.println(line); line = bufferedReader.readLine(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //最後一定要關閉 try { fileReader.close(); bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis();//結束時間 System.out.println("傳統IO讀取資料,不指定緩衝區大小,總共耗時:"+(end - start)+"ms"); } } }
2、傳統IO讀取資料,指定緩衝區大小
/** * 傳統IO讀取資料,指定緩衝區大小 * @author linbingwen * @since 2015年9月5日 * @param path * @return * @throws FileNotFoundException */ public static void readFile2(String path) throws FileNotFoundException {long start = System.currentTimeMillis();//開始時間int bufSize = 1024 * 1024 * 5;//5M緩衝區File fin = new File(path); // 檔案大小200MFileChannel fcin = new RandomAccessFile(fin, "r").getChannel();ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);String enterStr = "\n";long len = 0L;try {byte[] bs = new byte[bufSize];String tempString = null;while (fcin.read(rBuffer) != -1) {//每次讀5M到緩衝區int rSize = rBuffer.position();rBuffer.rewind();rBuffer.get(bs);//將緩衝區資料讀到數組中rBuffer.clear();//清除緩衝tempString = new String(bs, 0, rSize);int fromIndex = 0;//緩衝區起始int endIndex = 0;//緩衝區結束//按行讀緩衝區資料while ((endIndex = tempString.indexOf(enterStr, fromIndex)) != -1) {String line = tempString.substring(fromIndex, endIndex);//轉換一行System.out.print(line); fromIndex = endIndex + 1;}} long end = System.currentTimeMillis();//結束時間 System.out.println("傳統IO讀取資料,指定緩衝區大小,總共耗時:"+(end - start)+"ms");} catch (IOException e) {e.printStackTrace();}}
3、記憶體映射讀檔案
/** * NIO 記憶體映射讀大檔案 * @author linbingwen * @since 2015年9月15日 * @param path */public static void readFile3(String path) {long start = System.currentTimeMillis();//開始時間 long fileLength = 0; final int BUFFER_SIZE = 0x300000;// 3M的緩衝 File file = new File(path); fileLength = file.length(); try { MappedByteBuffer inputBuffer = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileLength);// 讀取大檔案 byte[] dst = new byte[BUFFER_SIZE];// 每次讀出3M的內容 for (int offset = 0; offset < fileLength; offset += BUFFER_SIZE) { if (fileLength - offset >= BUFFER_SIZE) { for (int i = 0; i < BUFFER_SIZE; i++) dst[i] = inputBuffer.get(offset + i); } else { for (int i = 0; i < fileLength - offset; i++) dst[i] = inputBuffer.get(offset + i); } // 將得到的3M內容給Scanner,這裡的XXX是指Scanner解析的分隔字元 Scanner scan = new Scanner(new ByteArrayInputStream(dst)).useDelimiter(" "); while (scan.hasNext()) { // 這裡為對讀取文本解析的方法 System.out.print(scan.next() + " "); } scan.close(); } System.out.println(); long end = System.currentTimeMillis();//結束時間 System.out.println("NIO 記憶體映射讀大檔案,總共耗時:"+(end - start)+"ms"); } catch (Exception e) { e.printStackTrace(); } }
三、測試對比
1、100M檔案
檔案大小如下:
調用如下:
public static void main(String args[]) {String path = "D:" + File.separator + "CES_T_MSM_LIQ-TRANS-ESP_20150702_01.DAT"; readFile1(path);//readFile2(path);//readFile3(path);}
(1)傳統IO讀取資料,不指定緩衝區大小,總共耗時:80264ms
其記憶體使用量如下:
(2)傳統IO讀取資料,指定緩衝區大小,總共耗時:80612ms
其記憶體使用量如下:
(3)NIO 記憶體映射讀大檔案,總共耗時:90955ms
其記憶體使用量如下:
分析發現記憶體映射並沒有比傳統IO快多少,甚至還更加慢了,有可能是因為磁碟IO操作多了,反而降低了其效率,記憶體映射看來還是對大檔案比較有好的效果。小檔案基本上是沒有多大的差別的。
2、1.2G檔案
傳統IO讀取資料,不指定緩衝區大小,總共耗時:1245111ms
NIO 記憶體映射讀大檔案,總共耗時:1223877ms(大概20分鐘多點)
著作權聲明:本文為博主林炳文Evankaka原創文章,轉載請註明出處http://blog.csdn.net/evankaka