java 深入理解內(nèi)存映射文件原理
內(nèi)存映射文件原理
首先說說這篇文章要解決什么問題?
1.虛擬內(nèi)存與內(nèi)存映射文件的區(qū)別與聯(lián)系.
2.內(nèi)存映射文件的原理.
3.內(nèi)存映射文件的效率.
4.傳統(tǒng)IO和內(nèi)存映射效率對比.
虛擬內(nèi)存與內(nèi)存映射文件的區(qū)別與聯(lián)系
二者的聯(lián)系
虛擬內(nèi)存和內(nèi)存映射文件都是將一部分內(nèi)容加載到,另一部分放在磁盤上的一種機(jī)制,二者都是應(yīng)用程序動態(tài)性的基礎(chǔ),由于二者的虛擬性,對于用戶都是透明的.
虛擬內(nèi)存其實(shí)就是硬盤的一部分,是計(jì)算機(jī)RAM與硬盤的數(shù)據(jù)交換區(qū),因?yàn)閷?shí)際的物理內(nèi)存可能遠(yuǎn)小于進(jìn)程的地址空間,這就需要把內(nèi)存中暫時不用到的數(shù)據(jù)放到硬盤上一個特殊的地方,當(dāng)請求的數(shù)據(jù)不在內(nèi)存中時,系統(tǒng)產(chǎn)生卻頁中斷,內(nèi)存管理器便將對應(yīng)的內(nèi)存頁重新從硬盤調(diào)入物理內(nèi)存。
內(nèi)存映射文件是由一個文件到一塊內(nèi)存的映射,使應(yīng)用程序可以通過內(nèi)存指針對磁盤上的文件進(jìn)行訪問,其過程就如同對加載了文件的內(nèi)存的訪問,因此內(nèi)存文件映射非常適合于用來管理大文件。
二者的區(qū)別
1.虛擬內(nèi)存使用硬盤只能是頁面文件,而內(nèi)存映射使用的磁盤部分可以是任何磁盤文件.
2.二者的架構(gòu)不同,或者是說應(yīng)用的場景不同,虛擬內(nèi)存是架構(gòu)在物理內(nèi)存之上,其引入是因?yàn)閷?shí)際的物理內(nèi)存運(yùn)行程序所需的空間,即使現(xiàn)在計(jì)算機(jī)中的物理內(nèi)存越來越大,程序的尺寸也在增長。將所有運(yùn)行著的程序全部加載到內(nèi)存中不經(jīng)濟(jì)也非常不現(xiàn)實(shí)。內(nèi)存映射文件架構(gòu)在程序的地址空間之上,32位機(jī)地址空間只有4G,而某些大文件的尺寸可要要遠(yuǎn)超出這個值,因此,用地址空間中的某段應(yīng)用文件中的一部分可解決處理大文件的問題,在32中,使用內(nèi)存映射文件可以處理2的64次(64EB)大小的文件.原因內(nèi)存映射文件,除了處理大文件,還可用作進(jìn)程間通信。
內(nèi)存映射文件的原理
“映射”就是建立一種對應(yīng)關(guān)系,在這里主要是指硬盤上文件的位置與進(jìn)程邏輯地址空間中一塊相同區(qū)域之間一一對應(yīng),這種關(guān)系純屬是邏輯上的概念,物理上是不存在的,原因是進(jìn)程的邏輯地址空間本身就是不存在的,在內(nèi)存映射過程中,并沒有實(shí)際的數(shù)據(jù)拷貝,文件沒有被載入內(nèi)存,只是邏輯上放入了內(nèi)存,具體到代碼,就是建立并初始化了相關(guān)的數(shù)據(jù)結(jié)構(gòu),這個過程有系統(tǒng)調(diào)用mmap()實(shí)現(xiàn),所以映射的效率很高.
內(nèi)存映射原理
上面說到建立內(nèi)存映射沒有進(jìn)行實(shí)際的數(shù)據(jù)拷貝,那么進(jìn)行進(jìn)程又怎么能最終通過內(nèi)存操作訪問到硬盤上的文件呢?
看上圖:
1.調(diào)用mmap(),相當(dāng)于要給進(jìn)行內(nèi)存映射的文件分配了虛擬內(nèi)存,它會返回一個指針ptr,這個ptr所指向的是一個邏輯地址,要操作其中的數(shù)據(jù),必須通過MMU將邏輯地址轉(zhuǎn)換成物理地址,如圖1中過程2所示。
2.建立內(nèi)存映射并沒有實(shí)際拷貝數(shù)據(jù),這時,MMU在地址映射表中是無法找到與ptr相對應(yīng)的物理地址的,也就是MMU失敗,將產(chǎn)生一個缺頁中斷,缺 頁中斷的中斷響應(yīng)函數(shù)會在swap中尋找相對應(yīng)的頁面,如果找不到(也就是該文件從來沒有被讀入內(nèi)存的情況),則會通過mmap()建立的映射關(guān)系,從硬 盤上將文件讀取到物理內(nèi)存中,如圖1中過程3所示。
3.如果在拷貝數(shù)據(jù)時,發(fā)現(xiàn)物理內(nèi)存不夠用,則會通過虛擬內(nèi)存機(jī)制(swap)將暫時不用的物理頁面交換到硬盤上,如圖1中過程4所示。
內(nèi)存映射文件的效率
了解過內(nèi)存映射文件都知道,它比傳統(tǒng)的IO讀寫數(shù)據(jù)快很多,那么,它為什么會這么快,從代碼層面上來看,從硬盤上將文件讀入內(nèi)存,都是要經(jīng)過數(shù)據(jù)拷貝,并且數(shù)據(jù)拷貝操作是由文件系統(tǒng)和硬件驅(qū)動實(shí)現(xiàn)的,理論上來說,拷貝數(shù)據(jù)的效率是一 樣的。其實(shí),原因是read()是系統(tǒng)調(diào)用,其中進(jìn)行了數(shù)據(jù) 拷貝,它首先將文件內(nèi)容從硬盤拷貝到內(nèi)核空間的一個緩沖區(qū),如圖2中過程1,然后再將這些數(shù)據(jù)拷貝到用戶空間,如圖2中過程2,在這個過程中,實(shí)際上完成 了兩次數(shù)據(jù)拷貝 ;而mmap()也是系統(tǒng)調(diào)用,如前所述,mmap()中沒有進(jìn)行數(shù)據(jù)拷貝,真正的數(shù)據(jù)拷貝是在缺頁中斷處理時進(jìn)行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數(shù)根據(jù)這個映射關(guān)系,直接將文件從硬盤拷貝到用戶空間,只進(jìn)行了 一次數(shù)據(jù)拷貝 。因此,內(nèi)存映射的效率要比read/write效率高。
read系統(tǒng)調(diào)用原理
傳統(tǒng)IO和內(nèi)存映射效率對比.
在這里,使用java傳統(tǒng)的IO,加緩沖區(qū)的IO,內(nèi)存映射分別讀取10M數(shù)據(jù).用時如下:
public class MapBufDelete { public static void main(String[] args) { try { FileInputStream fis=new FileInputStream("./largeFile.txt"); int sum=0; int n; long t1=System.currentTimeMillis(); try { while((n=fis.read())>=0){ // 數(shù)據(jù)處理 } } catch (IOException e) { e.printStackTrace(); } long t=System.currentTimeMillis()-t1; System.out.println("傳統(tǒng)IOread文件,不使用緩沖區(qū),用時:"+t); } catch (FileNotFoundException e) { e.printStackTrace(); } try { FileInputStream fis=new FileInputStream("./largeFile.txt"); BufferedInputStream bis=new BufferedInputStream(fis); int sum=0; int n; long t1=System.currentTimeMillis(); try { while((n=bis.read())>=0){ // 數(shù)據(jù)處理 } } catch (IOException e) { e.printStackTrace(); } long t=System.currentTimeMillis()-t1; System.out.println("傳統(tǒng)IOread文件,使用緩沖區(qū),用時:"+t); } catch (FileNotFoundException e) { e.printStackTrace(); } MappedByteBuffer buffer=null; try { buffer=new RandomAccessFile("./largeFile.txt","rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1253244); int sum=0; int n; long t1=System.currentTimeMillis(); for(int i=0;i<1024*1024*10;i++){ // 數(shù)據(jù)處理 } long t=System.currentTimeMillis()-t1; System.out.println("內(nèi)存映射文件讀取文件,用時:"+t); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { } } }
運(yùn)行結(jié)果
傳統(tǒng)IOread文件,不使用緩沖區(qū),用時:4739
傳統(tǒng)IOread文件,使用緩沖區(qū),用時:59
內(nèi)存映射文件讀取文件,用時:11
最后,解釋一下,為什么使用緩沖區(qū)讀取文件會比不使用快:
原因是每次進(jìn)行IO操作,都要從用戶態(tài)陷入內(nèi)核態(tài),由內(nèi)核把數(shù)據(jù)從磁盤中讀到內(nèi)核緩沖區(qū),再由內(nèi)核緩沖區(qū)到用戶緩沖區(qū),如果沒有buffer,讀取都需要從用戶態(tài)到內(nèi)核態(tài)切換,而這種切換很耗時,所以,采用預(yù)讀,減少IO次數(shù),如果有buffer,根據(jù)局部性原理,就會一次多讀數(shù)據(jù),放到緩沖區(qū)中,減少了IO次數(shù).
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
Android系統(tǒng)對話框使用詳解(最詳細(xì))
這篇文章主要介紹了Android系統(tǒng)對話框使用詳解(最詳細(xì)),需要的朋友可以參考下2017-10-10Android開發(fā)導(dǎo)入項(xiàng)目報錯Ignoring InnerClasses attribute for an anonym
今天小編就為大家分享一篇關(guān)于Android開發(fā)導(dǎo)入項(xiàng)目報錯Ignoring InnerClasses attribute for an anonymous inner class的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12