記憶體對映檔案(Memory-mapped File)
將一段虛擬記憶體逐位元組對映於一個檔案,使得應用程式處理檔案如同訪問主記憶體(但在真正使用到這些資料前卻不會消耗物理記憶體,也不會有讀寫磁碟的操作),這要比直接檔案讀寫快幾個數量級。
NIO記憶體對映的原理:
Linux下的系統呼叫:#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
實現檔案對映到程序地址空間,實現直接訪問檔案內容的功能;
對映的三種模式
1、MapMode.READ_ONLY,只讀模式;
2、MapMode.READ_WRITE,讀寫模式;
3、MapMode.PRIVATE,寫時複製(copy-on-write);
----透過put()方法所做的任何修改都會導致產生一個私有的資料複製並且該複製中的資料只有MappedByteBuffer例項可以看到;
----其它解釋:PRIVATE(專用): 對得到的緩衝區的更改不會傳播到檔案,並且該更改對對映到同一檔案的其他程式也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)
Copy-on-Write(寫時複製)
Copy-on-Write, 簡單來說是,複製一個物件時,不是真正的在記憶體中開闢一塊新的地址,將原來的物件複製到新地址,而是在新物件的記憶體對映表(Translation Table)中指向同原物件相同的位置,並且把那塊記憶體的copy-on-write位設定為1,在對這個物件執行讀操作的時候,記憶體資料沒有變動,直接執行就可以了。但是執行寫操作的時候,才將原物件複製一份到新地址,並且同時修改新物件的記憶體對映表到這個新位置,然後在進行寫入操作;
Copy-on-Write是建立在virtual memory 和paging的基礎上實現,優點比較顯而易見:節省記憶體空間,複製物件並不真正複製,相當於只建立了一個指標;
該技術只能適用於一小部分記憶體的page上,如果在複製新物件以後,大部分物件都還需要繼續進行寫操作 就會產生大量的分頁錯誤,得不償失;
通俗的解釋:
當多個使用者共享一塊相同的資料時,如果其中某個使用者要求對資料進行修改,系統會把這塊資料複製一份然後進行修改,修改完成後讓該使用者的記錄指向新修改的資料,其他使用者看到的還是原來的資料而該使用者看到的是已經修改的資料。 如果資料不寫只讀的話,不會被複制,這樣可以節省儲存空間。
記憶體對映的操作
fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
1、把檔案的從position開始的size大小的區域對映為記憶體映像檔案,即從0開始對映和檔案長度一樣到記憶體;
2、對映模式為:只讀;
2、此方法返回MappedByteBuffer;
Java記憶體對映MappedByteBuffer類
java.nio.MappedByteBuffer,繼承自:ByteBuffer;
java nio引入的檔案記憶體對映方案,讀寫效能極高。
MappedByteBuffer的3個方法
a. fore()======>緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入檔案;
b. load()======>將緩衝區的內容載入記憶體,並返回該緩衝區的引用;
c. isLoaded()======>如果緩衝區的內容在物理記憶體中,則返回真,否則返回假;
程式碼案例:package com.what21.nio.java.mmap.case01.mode01;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class MappedByteBuffer4ReadDemo { /** * @param args */ public static void main(String[] args) { String filePath = "F:\\MySoftware\\XXXX\\XXXXXX.exe"; String writeFilePath = "e:/XXXXX.exe"; // readFileByFileChannel(filePath,writeFilePath); // Read time :560ms // Write time :5070ms readFileByMappedByteBuffer(filePath, writeFilePath); // Read time :1ms // Write time :5169ms } /** * @param filePath * @param writeFilePath */ static void readFileByFileChannel(String filePath, String writeFilePath) { File file = new File(filePath); // 位元組快取區————————JVM內分配記憶體 ByteBuffer byteBuf = ByteBuffer.allocate((int) file.length()); // 位元組快取區————————JVM外分配記憶體(JVM的元資料區) // byteBuf = ByteBuffer.allocateDirect((int)file.length()); FileInputStream fis = null; FileChannel fileChannel = null; FileOutputStream fos = null; FileChannel outFileChannel = null; try { fis = new FileInputStream(file); fileChannel = fis.getChannel(); long timeStar = System.currentTimeMillis(); // 讀取到快取區 fileChannel.read(byteBuf); System.out.println(fileChannel.size()); long timeEnd = System.currentTimeMillis(); System.out.println("Read time :" + (timeEnd - timeStar) + "ms"); timeStar = System.currentTimeMillis(); fos = new FileOutputStream(writeFilePath); outFileChannel = fos.getChannel(); // 寫入之前先反轉 byteBuf.flip(); // 從快取區寫入到檔案 outFileChannel.write(byteBuf); timeEnd = System.currentTimeMillis(); System.out.println("Write time :" + (timeEnd - timeStar) + "ms"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { outFileChannel.close(); } catch (IOException e1) { e1.printStackTrace(); } try { fos.close(); } catch (IOException e1) { e1.printStackTrace(); } try { fileChannel.close(); } catch (IOException e) { e.printStackTrace(); } try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * @param filePath * @param writeFilePath */ static void readFileByMappedByteBuffer(String filePath, String writeFilePath) { File file = new File(filePath); MappedByteBuffer byteBuf = null; FileInputStream fis = null; FileChannel fileChannel = null; FileOutputStream fos = null; FileChannel outFileChannel = null; try { fis = new FileInputStream(file); fileChannel = fis.getChannel(); long timeStar = System.currentTimeMillis(); // 記憶體對映 byteBuf = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); System.out.println(fileChannel.size()); long timeEnd = System.currentTimeMillis(); System.out.println("Read time :" + (timeEnd - timeStar) + "ms"); timeStar = System.currentTimeMillis(); fos = new FileOutputStream(writeFilePath); outFileChannel = fos.getChannel(); // 從快取區寫入到檔案 outFileChannel.write(byteBuf); timeEnd = System.currentTimeMillis(); System.out.println("Write time :" + (timeEnd - timeStar) + "ms"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { outFileChannel.close(); } catch (IOException e1) { e1.printStackTrace(); } try { fos.close(); } catch (IOException e1) { e1.printStackTrace(); } try { fileChannel.close(); } catch (IOException e) { e.printStackTrace(); } try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }}
import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import java.util.Scanner;public class MappedByteBuffer4WriteDemo { /** * @param args */ public static void main(String[] args) { try { File file = new File("D://data.txt"); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); FileChannel channel = randomAccessFile.getChannel(); Scanner scan = new Scanner(System.in); int length = 0; while (true) { String line = scan.nextLine(); if ("q".equals(line) || "quit".equals(line)) { break; } System.out.printf("%s\n", line); // byte[] arr = (line + "\r\n").getBytes(); MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length + arr.length); mappedByteBuffer.position(length); mappedByteBuffer.put(arr); length = length + arr.length; } scan.close(); channel.close(); randomAccessFile.close(); } catch (IOException e) { e.printStackTrace(); } }}
import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import java.util.Scanner;public class MappedByteBuffer4PrivateDemo { /** * @param args */ public static void main(String[] args) { try { File file = new File("D://data.txt"); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); FileChannel channel = randomAccessFile.getChannel(); Scanner scan = new Scanner(System.in); int length = 0; while (true) { String line = scan.nextLine(); if ("q".equals(line) || "quit".equals(line)) { break; } System.out.printf("%s\n", line); // byte[] arr = (line + "\r\n").getBytes(); MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length + arr.length); mappedByteBuffer.position(length); mappedByteBuffer.put(arr); length = length + arr.length; } scan.close(); channel.close(); randomAccessFile.close(); } catch (IOException e) { e.printStackTrace(); } }}