Java NIO內(nèi)存映射---上G大文件處理

轉(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內(nèi)容