通常高性能的涉及到存儲框架,例如 RocketMQ,Kafka 這種消息隊列,存儲日誌的時候,都是經過 Java File MMAP 實現的,那麼什麼是 Java File MMAP 呢?java
儘管從JDK 1.4版本開始,Java 內存映射文件(Memory Mapped Files)就已經在java.nio
包中,但它對不少程序開發者來講仍然是一個至關新的概念。引入 NIO 後,Java IO 已經至關快,並且內存映射文件提供了 Java 有可能達到的最快 IO 操做,這也是爲何那些高性能 Java 應用應該使用內存映射文件來持久化數據。
做爲 NIO 的一個重要的功能,MMAP 方法爲咱們提供了將文件的部分或所有映射到內存地址空間的能力,同當這塊內存區域被寫入數據以後會變成髒頁,操做系統會用必定的算法把這些數據寫入到文件中,而咱們的 Java 程序不須要去關心這些。這就是內存映射文件的一個關鍵優點,即便你的程序在剛剛寫入內存後就掛了,操做系統仍然會將內存中的數據寫入文件系統。
另一個更突出的優點是共享內存,內存映射文件能夠被多個進程同時訪問,起到一種低時延共享內存的做用。git
package com.github.hashZhang.scanfold.jdk.file; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.Random; public class FileMmapTest { public static void main(String[] args) throws Exception { //記錄開始時間 long start = System.currentTimeMillis(); //經過RandomAccessFile的方式獲取文件的Channel,這種方式針對隨機讀寫的文件較爲經常使用,咱們用文件通常是隨機讀寫 RandomAccessFile randomAccessFile = new RandomAccessFile("./FileMmapTest.txt", "rw"); FileChannel channel = randomAccessFile.getChannel(); System.out.println("FileChannel初始化時間:" + (System.currentTimeMillis() - start) + "ms"); //內存映射文件,模式是READ_WRITE,若是文件不存在,就會被建立 MappedByteBuffer mappedByteBuffer1 = channel.map(FileChannel.MapMode.READ_WRITE, 0, 128 * 1024 * 1024); MappedByteBuffer mappedByteBuffer2 = channel.map(FileChannel.MapMode.READ_WRITE, 0, 128 * 1024 * 1024); System.out.println("MMAPFile初始化時間:" + (System.currentTimeMillis() - start) + "ms"); start = System.currentTimeMillis(); testFileChannelSequentialRW(channel); System.out.println("FileChannel順序讀寫時間:" + (System.currentTimeMillis() - start) + "ms"); start = System.currentTimeMillis(); testFileMMapSequentialRW(mappedByteBuffer1, mappedByteBuffer2); System.out.println("MMAPFile順序讀寫時間:" + (System.currentTimeMillis() - start) + "ms"); start = System.currentTimeMillis(); try { testFileChannelRandomRW(channel); System.out.println("FileChannel隨機讀寫時間:" + (System.currentTimeMillis() - start) + "ms"); } finally { randomAccessFile.close(); } //文件關閉不影響MMAP寫入和讀取 start = System.currentTimeMillis(); testFileMMapRandomRW(mappedByteBuffer1, mappedByteBuffer2); System.out.println("MMAPFile隨機讀寫時間:" + (System.currentTimeMillis() - start) + "ms"); } public static void testFileChannelSequentialRW(FileChannel fileChannel) throws Exception { byte[] bytes = "測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1".getBytes(); byte[] to = new byte[bytes.length]; //分配直接內存,減小複製 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length); //順序寫入 for (int i = 0; i < 100000; i++) { byteBuffer.put(bytes); byteBuffer.flip(); fileChannel.write(byteBuffer); byteBuffer.flip(); } fileChannel.position(0); //順序讀取 for (int i = 0; i < 100000; i++) { fileChannel.read(byteBuffer); byteBuffer.flip(); byteBuffer.get(to); byteBuffer.flip(); } } public static void testFileMMapSequentialRW(MappedByteBuffer mappedByteBuffer1, MappedByteBuffer mappedByteBuffer2) throws Exception { byte[] bytes = "測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2".getBytes(); byte[] to = new byte[bytes.length]; //順序寫入 for (int i = 0; i < 100000; i++) { mappedByteBuffer1.put(bytes); } //順序讀取 for (int i = 0; i < 100000; i++) { mappedByteBuffer2.get(to); } } public static void testFileChannelRandomRW(FileChannel fileChannel) throws Exception { try { byte[] bytes = "測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1測試字符串1".getBytes(); byte[] to = new byte[bytes.length]; //分配直接內存,減小複製 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length); //隨機寫入 for (int i = 0; i < 100000; i++) { byteBuffer.put(bytes); byteBuffer.flip(); fileChannel.position(new Random(i).nextInt(bytes.length*100000)); fileChannel.write(byteBuffer); byteBuffer.flip(); } //隨機讀取 for (int i = 0; i < 100000; i++) { fileChannel.position(new Random(i).nextInt(bytes.length*100000)); fileChannel.read(byteBuffer); byteBuffer.flip(); byteBuffer.get(to); byteBuffer.flip(); } } finally { fileChannel.close(); } } public static void testFileMMapRandomRW(MappedByteBuffer mappedByteBuffer1, MappedByteBuffer mappedByteBuffer2) throws Exception { byte[] bytes = "測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2測試字符串2".getBytes(); byte[] to = new byte[bytes.length]; //隨機寫入 for (int i = 0; i < 100000; i++) { mappedByteBuffer1.position(new Random(i).nextInt(bytes.length*100000)); mappedByteBuffer1.put(bytes); } //隨機讀取 for (int i = 0; i < 100000; i++) { mappedByteBuffer2.position(new Random(i).nextInt(bytes.length*100000)); mappedByteBuffer2.get(to); } } }
在這裏,咱們初始化了一個文件,並把它映射到了128M的內存中。分FileChannel還有MMAP的方式,經過順序或隨機讀寫,寫了一些內容並讀取一部份內容。github
運行結果是:算法
FileChannel初始化時間:7ms MMAPFile初始化時間:8ms FileChannel順序讀寫時間:420ms MMAPFile順序讀寫時間:20ms FileChannel隨機讀寫時間:860ms MMAPFile隨機讀寫時間:45ms
能夠看到,經過MMAP內存映射文件的方式操做文件,更加快速,而且性能提高的至關明顯。編程
微信搜索「個人編程喵」關注公衆號,每日一刷,輕鬆提高技術,斬獲各類offer:微信