內(nèi)存映射文件(Memory-mappedFile),指的是將一段虛擬內(nèi)存逐字節(jié)映射于一個文件,使得應(yīng)用程序處置文件好像訪問主內(nèi)存(但在真正運用到這些數(shù)據(jù)前卻不會耗費物理內(nèi)存,也不會有讀寫磁盤的操作),這要比直接文件讀寫快幾個數(shù)量級。
略微解釋一下虛擬內(nèi)存(很明顯,不是物理內(nèi)存),它是計算機系統(tǒng)內(nèi)存管理的一種技術(shù)。像施了妖法一樣使得應(yīng)用程序以為它具有連續(xù)的可用的內(nèi)存,實踐上呢,它通常是被分隔成多個物理內(nèi)存的碎片,還有局部暫時存儲在外部磁盤存儲器上,在需求時停止數(shù)據(jù)交流。
內(nèi)存映射文件主要的用途是增加I/O性能,特別是針對大文件。關(guān)于小文件,內(nèi)存映射文件反而會招致碎片空間的糜費,由于內(nèi)存映射總是要對齊頁邊境,最小單位是4KiB,一個5KiB的文件將會映射占用8KiB內(nèi)存,也就會糜費3KiB內(nèi)存。
java.nio包使得內(nèi)存映射變得十分簡單,其中的中心類叫做MappedByteBuffer,字面意義為映射的字節(jié)緩沖區(qū)。
01、運用MappedByteBuffer讀取文件
假定如今有一個文件,名叫cmower.txt,里面的內(nèi)容是:
緘默王二,一個有趣的程序員
PS:哎,改不了王婆賣瓜自賣自詡這個臭缺點了,由于文章被盜得都怕了。
這個文件放在/resource目錄下,我們能夠經(jīng)過下面的辦法獲取到它:
ClassLoaderclassLoader=Cmower.class.getClassLoader();
Pathpath=Paths.get(classLoader.getResource(“cmower.txt”).getPath());
Path既能夠表示一個目錄,也能夠表示一個文件,就像File那樣——當然了,Path是用來取代File的。
然后,從文件中獲取一個channel(通道,對磁盤文件的一種籠統(tǒng))。
FileChannelfileChannel=FileChannel.open(path);
緊接著,調(diào)用FileChannel類的map辦法從channel中獲取MappedByteBuffer,此類擴展了ByteBuffer——提供了一些內(nèi)存映射文件的根本操作辦法。
MappedByteBuffermappedByteBuffer=fileChannel.map(mode,position,size);
略微解釋一下map辦法的三個參數(shù)。
1)mode為文件映射形式,分為三種:
MapMode.READ_ONLY(只讀),任何試圖修正緩沖區(qū)的操作將招致拋出ReadOnlyBufferException異常。
MapMode.READ_WRITE(讀/寫),任何對緩沖區(qū)的更改都會在某個時辰寫入文件中。需求留意的是,其他映射同一個文件的程序可能不能立刻看到這些修正,多個程序同時停止文件映射的行為依賴于操作系統(tǒng)。
MapMode.PRIVATE(私有),對緩沖區(qū)的更改不會被寫入到該文件,任何修正對這個緩沖區(qū)來說都是私有的。
2)position為文件映射時的起始位置。
3)size為要映射的區(qū)域的大小,必需是非負數(shù),不得大于Integer.MAX_VALUE。
一旦把文件映射到內(nèi)存緩沖區(qū),我們就能夠把里面的數(shù)據(jù)讀入到CharBuffer中并打印出來。詳細的代碼示例如下。
CharBuffercharBuffer=null;
ClassLoaderclassLoader=Cmower.class.getClassLoader();
Pathpath=Paths.get(classLoader.getResource(“cmower.txt”).getPath());
try(FileChannelfileChannel=FileChannel.open(path)){
MappedByteBuffermappedByteBuffer=fileChannel.map(MapMode.READ_ONLY,0,fileChannel.size());
if(mappedByteBuffer!=null){
charBuffer=Charset.forName(“UTF-8”).decode(mappedByteBuffer);
}
System.out.println(charBuffer.toString());
}catch(IOExceptione){
e.printStackTrace();
}
由于decode()辦法的參數(shù)是MappedByteBuffer,這就意味著我們是從內(nèi)存中而不是磁盤中讀入的文件內(nèi)容,所以速度會十分快。
02、運用MappedByteBuffer寫入文件
假定如今要把下面的內(nèi)容寫入到一個文件,名叫cmower1.txt。
緘默王二,《Web全棧開發(fā)進階之路》作者
這個文件還沒有創(chuàng)立,方案放在項目的classpath目錄下。
Pathpath=Paths.get(“cmower1.txt”);
詳細位置見下圖所示。
然后,創(chuàng)立文件的通道。
FileChannelfileChannel=FileChannel.open(path,StandardOpenOption.READ,StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)
依然運用的open辦法,不過增加了3個參數(shù),前2個很好了解,表示文件可讀(READ)、可寫(WRITE);第3個參數(shù)TRUNCATE_EXISTING的意義是假如文件曾經(jīng)存在,并且文件曾經(jīng)翻開將要停止WRITE操作,則其長度被截斷為0。
緊接著,依然調(diào)用FileChannel類的map辦法從channel中獲取MappedByteBuffer。
MappedByteBuffermappedByteBuffer=fileChannel.map(MapMode.READ_WRITE,0,1024);
這一次,我們把形式調(diào)整為MapMode.READ_WRITE,并且指定文件大小為1024,即1KB的大小。然后運用MappedByteBuffer中的put()辦法將CharBuffer的內(nèi)容保管到文件中。詳細的代碼示例如下。
CharBuffercharBuffer=CharBuffer.wrap(“緘默王二,《Web全棧開發(fā)進階之路》作者”);
Pathpath=Paths.get(“cmower1.txt”);
try(FileChannelfileChannel=FileChannel.open(path,StandardOpenOption.READ,StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)){
MappedByteBuffermappedByteBuffer=fileChannel.map(MapMode.READ_WRITE,0,1024);
if(mappedByteBuffer!=null){
mappedByteBuffer.put(Charset.forName(“UTF-8”).encode(charBuffer));
}
}catch(IOExceptione){
e.printStackTrace();
}
能夠翻開cmower1.txt查看一下內(nèi)容,確認預(yù)期的內(nèi)容有沒有寫入勝利。
03、MappedByteBuffer的遺憾
聽說,在Java中運用MappedByteBuffer是一件十分費事并且痛苦的事,主要表現(xiàn)有:
1)一次map的大小最好限制在1.5G左右,反復(fù)map會增加虛擬內(nèi)存回收和重新分配的壓力。也就是說,假如文件大小不肯定的話,就不太友好。
2)虛擬內(nèi)存由操作系統(tǒng)來決議什么時分刷新到磁盤,這個時間不太容易被程序控制。
3)MappedByteBuffer的回收方式比擬詭異。
再次強調(diào),這三種說法都是聽說,我暫時才能有限,也不能肯定這種說法的精確性,很遺憾。
04、比擬文件操作的處置時間
嗨,朋友,閱讀完以上的內(nèi)容之后,我想你一定對內(nèi)存映射文件有了大致的理解。但我置信,假如你是一名擔任任的程序員,你一定還想曉得:內(nèi)存映射文件的讀取速度終究有多快。
為了得出結(jié)論,我叫了另外三名競賽的選手:InputStream(普通輸入流)、BufferedInputStream(帶緩沖的輸入流)、RandomAccessFile(隨機訪問文件)。
讀取的對象是加勒比海盜4驚濤怪浪.mkv,大小為1.71G。
1)普通輸入流
publicstaticvoidinputStream(Pathfilename){
try(InputStreamis=Files.newInputStream(filename)){
intc;
while((c=is.read())!=-1){
}
}catch(IOExceptione){
e.printStackTrace();
}
}
2)帶緩沖的輸入流
publicstaticvoidbufferedInputStream(Pathfilename){
try(InputStreamis=newBufferedInputStream(Files.newInputStream(filename))){
intc;
while((c=is.read())!=-1){
}
}catch(IOExceptione){
e.printStackTrace();
}
}
3)隨機訪問文件
publicstaticvoidrandomAccessFile(Pathfilename){
try(RandomAccessFilerandomAccessFile=newRandomAccessFile(filename.toFile(),”r”)){
for(longi=0;i<randomAccessFile.length();i++){
randomAccessFile.seek(i);
}
}catch(IOExceptione){
e.printStackTrace();
}
}
4)內(nèi)存映射文件
publicstaticvoidmappedFile(Pathfilename){
try(FileChannelfileChannel=FileChannel.open(filename)){
longsize=fileChannel.size();
MappedByteBuffermappedByteBuffer=fileChannel.map(MapMode.READ_ONLY,0,size);
for(inti=0;i<size;i++){
mappedByteBuffer.get(i);
}
}catch(IOExceptione){
e.printStackTrace();
}
}
測試程序也很簡單,大致如下:
longstart=System.currentTimeMillis();
bufferedInputStream(Paths.get(“jialebi.mkv”));
longend=System.currentTimeMillis();
System.out.println(end-start);
四名選手的結(jié)果如下表所示。
辦法時間
普通輸入流龜速,沒有耐煩等出結(jié)果
隨機訪問文件龜速,沒有耐煩等下去
帶緩沖的輸入流29966
內(nèi)存映射文件914
普通輸入流和隨機訪問文件都慢得要命,真的是龜速,我沒有耐煩等候出結(jié)果;帶緩沖的輸入流的表現(xiàn)還不錯,但相比內(nèi)存映射文件就遜色多了。由此得出的結(jié)論就是:內(nèi)存映射文件,上G大文件輕松處置。
05、最后
本篇文章主要引見了Java的內(nèi)存映射文件,MappedByteBuffer是其靈魂,讀取速度快如火箭。另外,一切這些示例和代碼片段都能夠在GitHub上找到——這是一個Maven項目,所以它很容易導(dǎo)入和運轉(zhuǎn)。
廣州天河區(qū)珠江新城富力盈力大廈北塔2706
020-38013166(網(wǎng)站咨詢專線)
400-001-5281 (售后服務(wù)熱線)
深圳市坂田十二橡樹莊園F1-7棟
Site/ http://www.szciya.com
E-mail/ itciya@vip.163.com
品牌服務(wù)專線:400-001-5281
長沙市天心區(qū)芙蓉中路三段398號新時空大廈5樓
聯(lián)系電話/ (+86 0731)88282200
品牌服務(wù)專線/ 400-966-8830
旗下運營網(wǎng)站:
Copyright ? 2016 廣州思洋文化傳播有限公司,保留所有權(quán)利。 粵ICP備09033321號