轉(zhuǎn)至:http://blog.csdn.net/evankaka/article/details/48464013
版權(quán)聲明:本文為博主林炳文Evankaka原創(chuàng)文章,轉(zhuǎn)載請注明出處http://blog.csdn.net/evankaka
林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載請注明出處http://blog.csdn.net/evankaka
摘要:本文主要講了Java中內(nèi)存映射的原理及過程,與傳統(tǒng)IO進行了對比,最后,用實例說明了結(jié)果。
一、java中的內(nèi)存映射IO和內(nèi)存映射文件是什么?
內(nèi)存映射文件非常特別,它允許Java程序直接從內(nèi)存中讀取文件內(nèi)容,通過將整個或部分文件映射到內(nèi)存,由操作系統(tǒng)來處理加載請求和寫入文件,應(yīng)用只需要和內(nèi)存打交道,這使得IO操作非常快。加載內(nèi)存映射文件所使用的內(nèi)存在Java堆區(qū)之外。Java編程語言支持內(nèi)存映射文件,通過java.nio包和MappedByteBuffer 可以從內(nèi)存直接讀寫文件。
內(nèi)存映射文件
內(nèi)存映射文件,是由一個文件到一塊內(nèi)存的映射。Win32提供了允許應(yīng)用程序把文件映射到一個進程的函數(shù) (CreateFileMapping)。內(nèi)存映射文件與虛擬內(nèi)存有些類似,通過內(nèi)存映射文件可以保留一個地址空間的區(qū)域,同時將物理存儲器提交給此區(qū)域,內(nèi)存文件映射的物理存儲器來自一個已經(jīng)存在于磁盤上的文件,而且在對該文件進行操作之前必須首先對文件進行映射。使用內(nèi)存映射文件處理存儲于磁盤上的文件時,將不必再對文件執(zhí)行I/O操作,使得內(nèi)存映射文件在處理大數(shù)據(jù)量的文件時能起到相當(dāng)重要的作用。
內(nèi)存映射IO
在傳統(tǒng)的文件IO操作中,我們都是調(diào)用操作系統(tǒng)提供的底層標(biāo)準(zhǔn)IO系統(tǒng)調(diào)用函數(shù) read()、write() ,此時調(diào)用此函數(shù)的進程(在JAVA中即java進程)由當(dāng)前的用戶態(tài)切換到內(nèi)核態(tài),然后OS的內(nèi)核代碼負(fù)責(zé)將相應(yīng)的文件數(shù)據(jù)讀取到內(nèi)核的IO緩沖區(qū),然 后再把數(shù)據(jù)從內(nèi)核IO緩沖區(qū)拷貝到進程的私有地址空間中去,這樣便完成了一次IO操作。這么做是為了減少磁盤的IO操作,為了提高性能而考慮的,因為我們的程序訪問一般都帶有局部性,也就是所 謂的局部性原理,在這里主要是指的空間局部性,即我們訪問了文件的某一段數(shù)據(jù),那么接下去很可能還會訪問接下去的一段數(shù)據(jù),由于磁盤IO操作的速度比直接 訪問內(nèi)存慢了好幾個數(shù)量級,所以O(shè)S根據(jù)局部性原理會在一次 read()系統(tǒng)調(diào)用過程中預(yù)讀更多的文件數(shù)據(jù)緩存在內(nèi)核IO緩沖區(qū)中,當(dāng)繼續(xù)訪問的文件數(shù)據(jù)在緩沖區(qū)中時便直接拷貝數(shù)據(jù)到進程私有空間,避免了再次的低 效率磁盤IO操作。其過程如下
內(nèi)存映射文件和之前說的 標(biāo)準(zhǔn)IO操作最大的不同之處就在于它雖然最終也是要從磁盤讀取數(shù)據(jù),但是它并不需要將數(shù)據(jù)讀取到OS內(nèi)核緩沖區(qū),而是直接將進程的用戶私有地址空間中的一 部分區(qū)域與文件對象建立起映射關(guān)系,就好像直接從內(nèi)存中讀、寫文件一樣,速度當(dāng)然快了。
內(nèi)存映射的優(yōu)缺點
內(nèi)存映射IO最大的優(yōu)點可能在于性能,這對于建立高頻電子交易系統(tǒng)尤其重要。內(nèi)存映射文件通常比標(biāo)準(zhǔn)通過正常IO訪問文件要快。另一個巨大的優(yōu)勢是內(nèi)存映 射IO允許加載不能直接訪問的潛在巨大文件 。經(jīng)驗表明,內(nèi)存映射IO在大文件處理方面性能更加優(yōu)異。盡管它也有不足——增加了頁面錯誤的數(shù)目。由于操作系統(tǒng)只將一部分文件加載到內(nèi)存,如果一個請求 頁面沒有在內(nèi)存中,它將導(dǎo)致頁面錯誤。同樣它可以被用來在兩個進程中共享數(shù)據(jù)。
支持內(nèi)存映射IO的操作系統(tǒng)
大多數(shù)主流操作系統(tǒng)比如Windows平臺,UNIX,Solaris和其他類UNIX操作系統(tǒng)都支持內(nèi)存映射IO和64位架構(gòu),你幾乎可以將所有文件映射到內(nèi)存并通過JAVA編程語言直接訪問。
Java的內(nèi)存映射IO的要點
如下為一些你需要了解的java內(nèi)存映射要點:
java通過java.nio包來支持內(nèi)存映射IO。
內(nèi)存映射文件主要用于性能敏感的應(yīng)用,例如高頻電子交易平臺。
通過使用內(nèi)存映射IO,你可以將大文件加載到內(nèi)存。
內(nèi)存映射文件可能導(dǎo)致頁面請求錯誤,如果請求頁面不在內(nèi)存中的話。
映射文件區(qū)域的能力取決于于內(nèi)存尋址的大小。在32位機器中,你不能訪問超過4GB或2 ^ 32(以上的文件)。
內(nèi)存映射IO比起Java中的IO流要快的多。
加載文件所使用的內(nèi)存是Java堆區(qū)之外,并駐留共享內(nèi)存,允許兩個不同進程共享文件。
內(nèi)存映射文件讀寫由操作系統(tǒng)完成,所以即使在將內(nèi)容寫入內(nèi)存后java程序崩潰了,它將仍然會將它寫入文件直到操作系統(tǒng)恢復(fù)。
出于性能考慮,推薦使用直接字節(jié)緩沖而不是非直接緩沖。
不要頻繁調(diào)用MappedByteBuffer.force()方法,這個方法意味著強制操作系統(tǒng)將內(nèi)存中的內(nèi)容寫入磁盤,所以如果你每次寫入內(nèi)存映射文件都調(diào)用force()方法,你將不會體會到使用映射字節(jié)緩沖的好處,相反,它(的性能)將類似于磁盤IO的性能。
萬一發(fā)生了電源故障或主機故障,將會有很小的機率發(fā)生內(nèi)存映射文件沒有寫入到磁盤,這意味著你可能會丟失關(guān)鍵數(shù)據(jù)。
1、傳統(tǒng)IO讀取數(shù)據(jù),不指定緩沖區(qū)大小
[java]view plaincopy
/**
*?傳統(tǒng)IO讀取數(shù)據(jù),不指定緩沖區(qū)大小
*?@author?linbingwen
*?@since??2015年9月5日
*?@param?path
*?@return
*/
publicstaticvoidreadFile1(String?path)?{
longstart?=?System.currentTimeMillis();//開始時間
File?file?=newFile(path);
if(file.isFile())?{
BufferedReader?bufferedReader?=null;
FileReader?fileReader?=null;
try{
fileReader?=newFileReader(file);
bufferedReader?=newBufferedReader(fileReader);
String?line?=?bufferedReader.readLine();
System.out.println("==========================?傳統(tǒng)IO讀取數(shù)據(jù),使用虛擬機堆內(nèi)存?==========================");
while(line?!=null)?{//按行讀數(shù)據(jù)
System.out.println(line);
line?=?bufferedReader.readLine();
}
}catch(FileNotFoundException?e)?{
e.printStackTrace();
}catch(IOException?e)?{
e.printStackTrace();
}finally{
//最后一定要關(guān)閉
try{
fileReader.close();
bufferedReader.close();
}catch(IOException?e)?{
e.printStackTrace();
}
longend?=?System.currentTimeMillis();//結(jié)束時間
System.out.println("傳統(tǒng)IO讀取數(shù)據(jù),不指定緩沖區(qū)大小,總共耗時:"+(end?-?start)+"ms");
}
}
}
2、傳統(tǒng)IO讀取數(shù)據(jù),指定緩沖區(qū)大小
[java]view plaincopy
/**
*?傳統(tǒng)IO讀取數(shù)據(jù),指定緩沖區(qū)大小
*?@author?linbingwen
*?@since??2015年9月5日
*?@param?path
*?@return
*?@throws?FileNotFoundException
*/
publicstaticvoidreadFile2(String?path)throwsFileNotFoundException?{
longstart?=?System.currentTimeMillis();//開始時間
intbufSize?=1024*1024*5;//5M緩沖區(qū)
File?fin?=newFile(path);//?文件大小200M
FileChannel?fcin?=newRandomAccessFile(fin,"r").getChannel();
ByteBuffer?rBuffer?=?ByteBuffer.allocate(bufSize);
String?enterStr?="\n";
longlen?=?0L;
try{
byte[]?bs?=newbyte[bufSize];
String?tempString?=null;
while(fcin.read(rBuffer)?!=?-1)?{//每次讀5M到緩沖區(qū)
intrSize?=?rBuffer.position();
rBuffer.rewind();
rBuffer.get(bs);//將緩沖區(qū)數(shù)據(jù)讀到數(shù)組中
rBuffer.clear();//清除緩沖
tempString?=newString(bs,0,?rSize);
intfromIndex?=0;//緩沖區(qū)起始
intendIndex?=0;//緩沖區(qū)結(jié)束
//按行讀緩沖區(qū)數(shù)據(jù)
while((endIndex?=?tempString.indexOf(enterStr,?fromIndex))?!=?-1)?{
String?line?=?tempString.substring(fromIndex,?endIndex);//轉(zhuǎn)換一行
System.out.print(line);
fromIndex?=?endIndex?+1;
}
}
longend?=?System.currentTimeMillis();//結(jié)束時間
System.out.println("傳統(tǒng)IO讀取數(shù)據(jù),指定緩沖區(qū)大小,總共耗時:"+(end?-?start)+"ms");
}catch(IOException?e)?{
e.printStackTrace();
}
}
3、內(nèi)存映射讀文件
[java]view plaincopy
/**
*?NIO?內(nèi)存映射讀大文件
*?@author?linbingwen
*?@since??2015年9月15日
*?@param?path
*/
publicstaticvoidreadFile3(String?path)?{
longstart?=?System.currentTimeMillis();//開始時間
longfileLength?=0;
finalintBUFFER_SIZE?=0x300000;//?3M的緩沖
File?file?=newFile(path);
fileLength?=?file.length();
try{
MappedByteBuffer?inputBuffer?=newRandomAccessFile(file,"r").getChannel().map(FileChannel.MapMode.READ_ONLY,0,?fileLength);//?讀取大文件
byte[]?dst?=newbyte[BUFFER_SIZE];//?每次讀出3M的內(nèi)容
for(intoffset?=0;?offset?<?fileLength;?offset?+=?BUFFER_SIZE)?{
if(fileLength?-?offset?>=?BUFFER_SIZE)?{
for(inti?=0;?i?<?BUFFER_SIZE;?i++)
dst[i]?=?inputBuffer.get(offset?+?i);
}else{
for(inti?=0;?i?<?fileLength?-?offset;?i++)
dst[i]?=?inputBuffer.get(offset?+?i);
}
//?將得到的3M內(nèi)容給Scanner,這里的XXX是指Scanner解析的分隔符
Scanner?scan?=newScanner(newByteArrayInputStream(dst)).useDelimiter("?");
while(scan.hasNext())?{
//?這里為對讀取文本解析的方法
System.out.print(scan.next()?+"?");
}
scan.close();
}
System.out.println();
longend?=?System.currentTimeMillis();//結(jié)束時間
System.out.println("NIO?內(nèi)存映射讀大文件,總共耗時:"+(end?-?start)+"ms");
}catch(Exception?e)?{
e.printStackTrace();
}
}
1、100M文件
文件大小如下:
調(diào)用如下:
[java]view plaincopy
publicstaticvoidmain(String?args[])?{
String?path?="D:"+?File.separator?+"CES_T_MSM_LIQ-TRANS-ESP_20150702_01.DAT";
readFile1(path);
//readFile2(path);
//readFile3(path);
}
(1)傳統(tǒng)IO讀取數(shù)據(jù),不指定緩沖區(qū)大小,總共耗時:80264ms
其內(nèi)存使用如下:
(2)傳統(tǒng)IO讀取數(shù)據(jù),指定緩沖區(qū)大小,總共耗時:80612ms
其內(nèi)存使用如下:
(3)NIO 內(nèi)存映射讀大文件,總共耗時:90955ms
其內(nèi)存使用如下:
分析發(fā)現(xiàn)內(nèi)存映射并沒有比傳統(tǒng)IO快多少,甚至還更加慢了,有可能是因為磁盤IO操作多了,反而降低了其效率,內(nèi)存映射看來還是對大文件比較有好的效果。小文件基本上是沒有多大的差別的。
2、1.2G文件
傳統(tǒng)IO讀取數(shù)據(jù),不指定緩沖區(qū)大小,總共耗時:1245111ms
NIO 內(nèi)存映射讀大文件,總共耗時:1223877ms(大概20分鐘多點)
頂
5
踩
0