日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java基础:JavaNIO 之 内存映射文件原理

發布時間:2025/4/16 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java基础:JavaNIO 之 内存映射文件原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 前言

最近研究Java中內存映射I/O。Java類庫中的NIO中的內存映射文件MappedByteBuffer,相對于Java I/O是一個新的功能。特把適合用于處理大文件,在對大文件處理的時候效率極高。本文章將從操作系統I/O調用原理講解為什么內存映射文件MappedByteBuffer相比較Java I/O性能極高。話不多說,我們開始學習吧。

2. 淺談Java I/O InputStream Read調用

在Java I/O InputStream中我們可以調用Java API中Read、Write函數進行讀取數據流(文件流、網絡Socket流)。其實我們從源碼中可以分析出來,InputStream(比如FileInputStream)對于Java API Read和Write函數的調用,最終是調用JNI(Java Native Interface)的read、write系統調用。JNI,它提供了若干的API實現了Java和其他語言的通信(主要是C&C++)。最終調用操作系統底層系統調用。

在傳統的文件IO操作中,我們都是調用操作系統提供的底層標準IO系統調用函數 read()、write() ,此時調用此函數的進程(在JAVA中即java進程)由當前的用戶態切換到內核態,然后OS的內核代碼負責將相應的文件數據讀取到內核的IO緩沖區,然后再把數據從內核IO緩沖區拷貝到進程的私有地址空間中去,也就是拷貝到了用戶緩沖區,這樣便完成了一次IO操作。

至于為什么要多此一舉搞一個內核IO緩沖區把原本只需一次拷貝數據的事情搞成需要2次數據拷貝呢? 我想學過操作系統或者計算機系統結構的人都知道,這么做是為了減少磁盤的IO操作,為了提高性能而考慮的,因為我們的程序訪問一般都帶有局部性,也就是所謂的局部性原理,在這里主要是指的空間局部性,即我們訪問了文件的某一段數據,那么接下去很可能還會訪問接下去的一段數據,由于磁盤IO操作的速度比直接訪問內存慢了好幾個數量級,所以OS根據局部性原理會在一次 read()系統調用過程中預讀更多的文件數據緩存在內核IO緩沖區中,當繼續訪問的文件數據在緩沖區中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操作。在JAVA中當我們采用IO包下的文件操作流,如:

FileInputStream in = new FileInputStream("D:\\java.txt"); in.read();

但是這種I/O調用存在頻繁操作系統調用,并且每次操作系統調用需要從用戶態切換到內核態的問題,因為我們需要不停的read數據,就要不停的調用操作系統底層函數read()、write()函數。為了解決這個問題,Java I/O提供了一種BufferedInputStream這種裝飾器類,這個類中會有一段事先緩存的byte[]數據,這樣一來,我們每一次 buf_in.read() 時候,BufferedInputStream 會根據情況自動為我們預讀更多的字節數據到它自己維護的一個內部字節數組緩沖區中,這樣我們便可以減少系統調用次數,相應減少了用戶態程序切換到內核態,從而達到其緩沖區的目的。

FileInputStream in = new FileInputStream("D:\\java.txt"); BufferedInputStream buf_in = new BufferedInputStream(in); buf_in.read(); public class BufferedInputStream extends FilterInputStream { private static int defaultBufferSize = 8192; /** * The internal buffer array where the data is stored. When necessary, * it may be replaced by another array of * a different size. */ protected volatile byte buf[]; /** * See * the general contract of the <code>read</code> * method of <code>InputStream</code>. * * @return the next byte of data, or <code>-1</code> if the end of the * stream is reached. * @exception IOException if this input stream has been closed by * invoking its {@link #close()} method, * or an I/O error occurs. * @see java.io.FilterInputStream#in */ public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }

我們可以看到,BufferedInputStream 內部維護著一個 字節數組 byte[] buf 來實現緩沖區的功能,我們調用的 buf_in.read() 方法在返回數據之前有做一個 if 判斷,如果 buf 數組的當前索引不在有效的索引范圍之內,即 if 條件成立, buf 字段維護的緩沖區已經不夠了,這時候會調用 內部的 fill() 方法進行填充,而fill()會預讀更多的數據到 buf 數組緩沖區中去,然后再返回當前字節數據,如果 if 條件不成立便直接從 buf緩沖區數組返回數據了。其中getBufIfOpen()返回的就是 buf字段的引用。順便說下,源碼中的 buf 字段聲明為 protected volatile byte buf[]; 主要是為了通過 volatile 關鍵字保證 buf數組在多線程并發環境中的內存可見性.

3. 內存映射文件

BufferedInputStream雖然解決了進程進行頻繁的操作系統底層調用的問題(因為我們可以從緩沖區中讀取數據,不用進行操作系統read調用),但是他還是不能解決數據從磁盤讀取到內核緩沖區,然后內核緩沖區的數據復制移動到用戶空間緩沖區。程序還是需要從用戶態切換到內核態,然后再進行操作系統調用,并且數據移動和復制了兩次。

Java NIO在此后采用了MappedByteBuffer用來解決這個,內存映射文件和之前說的 標準IO操作最大的不同之處就在于它雖然最終也是要從磁盤讀取數據,但是它并不需要將數據讀取到OS內核緩沖區,而是直接將進程的用戶私有地址空間中的一部分區域與文件對象建立起映射關系,就好像直接從內存中讀、寫文件一樣,速度當然快了。為了說清楚這個,我們以 Linux操作系統為例子,看下圖:

內存映射IO原理:此圖為 Linux 2.X 中的進程虛擬存儲器,即進程的虛擬地址空間,如果你的機子是 32 位,那么就有 2^32 = 4G的虛擬地址空間,我們可以看到圖中有一塊區域: “Memory mapped region for shared libraries” ,這段區域就是在內存映射文件的時候將某一段的虛擬地址和文件對象的某一部分建立起映射關系,此時并沒有拷貝數據到內存中去,而是當進程代碼第一次引用這段代碼內的虛擬地址時,觸發了缺頁異常,這時候OS根據映射關系直接將文件的相關部分數據拷貝到進程的用戶私有空間中去,當有操作第N頁數據的時候重復這樣的OS頁面調度程序操作。注意啦,原來內存映射文件的效率比標準IO高的重要原因就是因為少了把數據拷貝到OS內核緩沖區這一步,數據直接被拷貝到進程的地址空間中。

4. MpappedByteBuffer

java中提供了3種內存映射模式,即:只讀(readonly)、讀寫(read_write)、專用(private) ,對于 只讀模式來說,如果程序試圖進行寫操作,則會拋出ReadOnlyBufferException異常;第二種的讀寫模式表明了通過內存映射文件的方式寫或修改文件內容的話是會立刻反映到磁盤文件中去的,別的進程如果共享了同一個映射文件,那么也會立即看到變化!而不是像標準IO那樣每個進程有各自的內核緩沖區,比如JAVA代碼中,沒有執行 IO輸出流的 flush() 或者 close() 操作,那么對文件的修改不會更新到磁盤去,除非進程運行結束;最后一種專用模式采用的是OS的“寫時拷貝”原則,即在沒有發生寫操作的情況下,多個進程之間都是共享文件的同一塊物理內存(進程各自的虛擬地址指向同一片物理地址),一旦某個進程進行寫操作,那么將會把受影響的文件數據單獨拷貝一份到進程的私有緩沖區中,不會反映到物理文件中去。

java中提供了3種內存映射模式,即:只讀(readonly)、讀寫(read_write)、專用(private) ,對于 只讀模式來說,如果程序試圖進行寫操作,則會拋出ReadOnlyBufferException異常;第二種的讀寫模式表明了通過內存映射文件的方式寫或修改文件內容的話是會立刻反映到磁盤文件中去的,別的進程如果共享了同一個映射文件,那么也會立即看到變化!而不是像標準IO那樣每個進程有各自的內核緩沖區,比如JAVA代碼中,沒有執行 IO輸出流的 flush() 或者 close() 操作,那么對文件的修改不會更新到磁盤去,除非進程運行結束;最后一種專用模式采用的是OS的“寫時拷貝”原則,即在沒有發生寫操作的情況下,多個進程之間都是共享文件的同一塊物理內存(進程各自的虛擬地址指向同一片物理地址),一旦某個進程進行寫操作,那么將會把受影響的文件數據單獨拷貝一份到進程的私有緩沖區中,不會反映到物理文件中去。

在JAVA NIO中可以很容易的創建一塊內存映射區域,代碼如下:

File file = new File("E:\\download\\office2007pro.chs.ISO"); FileInputStream in = new FileInputStream(file); FileChannel channel = in.getChannel(); MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 0,channel.size());

按照jdk文檔的官方說法,內存映射文件屬于JVM中的直接緩沖區,還可以通過 ByteBuffer.allocateDirect() ,即DirectMemory的方式來創建直接緩沖區。他們相比基礎的 IO操作來說就是少了中間緩沖區的數據拷貝開銷。同時他們屬于JVM堆外內存,不受JVM堆內存大小的限制。

總結

以上是生活随笔為你收集整理的Java基础:JavaNIO 之 内存映射文件原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。