Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

1.优点

与常规I / O相比,内存映射IO具有以下优点:

  • 用户进程将文件数据视为内存,因此无需发出read()或write()系统调用。
  • 当用户进程触摸映射的内存空间时,将自动生成页错误,以从磁盘引入文件数据。如果用户修改了映射的内存空间,则受影响的页面会自动标记为脏页面,随后将刷新到磁盘以更新文件。
  • 操作系统的虚拟内存子系统将执行页面的智能缓存,并根据系统负载自动管理内存。
  • 数据始终是页面对齐的,不需要复制缓冲区。
  • 可以映射非常大的文件,而无需消耗大量内存来复制数据。



2.示例代码——写

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
 
public class MemoryMappedFileWriteExample {
    private static String bigTextFile = "test.txt";
 
    public static void main(String[] args) throws Exception 
    {
        // Create file object
        File file = new File(bigTextFile);
         
        //Delete the file; we will create a new file
        file.delete();
                     
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"))
        {
            // Get file channel in read-write mode
            FileChannel fileChannel = randomAccessFile.getChannel();
 
            // Get direct byte buffer access using channel.map() operation
            MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 8 * 8);
 
            //Write the content using put methods
            buffer.put("howtodoinjava.com".getBytes());
        }
    }
}

3.示例代码——读

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
 
public class MemoryMappedFileReadExample 
{
    private static String bigExcelFile = "bigFile.xls";
 
    public static void main(String[] args) throws Exception 
    {
        try (RandomAccessFile file = new RandomAccessFile(new File(bigExcelFile), "r"))
        {
            //Get file channel in read-only mode
            FileChannel fileChannel = file.getChannel();
             
            //Get direct byte buffer access using channel.map() operation
            MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
             
            // the buffer now reads the file as if it were loaded in memory. 
            System.out.println(buffer.isLoaded());  //prints false
            System.out.println(buffer.capacity());  //Get the size based on content size of file
             
            //You can read the file from this buffer the way you like.
            for (int i = 0; i < buffer.limit(); i++)
            {
                System.out.print((char) buffer.get()); //Print the content of file
            }
        }
    }
}

4.参考资料

https://howtodoinjava.com/java/nio/memory-mapped-files-mappedbytebuffer/

https://xunnanxu.github.io/2016/09/10/It-s-all-about-buffers-zero-copy-mmap-and-Java-NIO/


5.实例分析——MangoDB

(1)文件在磁盘上的分布情况

  • 每个Database(DB)由一个.ns文件及若干个数据文件组成
  • 数据文件从0开始编号,依次为mydb.0、mydb.1、mydb.2等,文件大小从64MB起,依次倍增,最大为2GB。
  • 每个DB包含多个namespace(对应mongodb的collection名),mydb.ns实际上是一个hash表(采用线性探测方式解决冲突),用于快速定位某个namespace的起始位置。
  • namespace元数据结构如下:
class NamespaceDetails {
    DiskLoc firstExtent; // 第一个extent位置
    DiskLoc lastExtent;  // 最后一个extent位置
    DiskLoc deletedListSmall[SmallBuckets]; 
    // 不同大小的删除记录列表
    ...
};
  • 其中DiskLoc代表某个数据文件的具体偏移位置,数据文件使用mmap映射到内存空间进行管理,内存的管理(哪些数据何时换入/换出)完全交给OS管理。
class DiskLoc {
    int _a;  // 数据文件编号,如mydb.0编号为0
    int ofs; // 文件内部偏移
};

(2)文件内部组织结构

  • 每个数据文件被划分成多个extent,每个extent只包含一个namespace的数据,同一个namespace的所有extent之间以双向链表形式组织。
  • namesapce的元数据里包含指向第一个及最后一个extent的位置指针,通过这些信息,就可以遍历一个namespace下的所有extent数据。
  • 每个数据文件包含一个固定长度头部DataFileHeader
class DataFileHeader {
    DataFileVersion version;
    int fileLength;
    DiskLoc unused;
    int unusedLength;
    DiskLoc freeListStart;
    DiskLoc freeListEnd;
    char reserve[];
 };
  • Record被删除后,会以DeleteRecord的形式存储,其前两个字段与Record是一致的。
class DeletedRecord {
   int _lengthWithHeaders;  // record长度
   int _extentOfs;          // record所在的extent位置指针
   DiskLoc _nextDeleted;    // 下一个已删除记录的位置
};
  • 一个namespace下的所有的已删除记录(可以回收并复用的存储空间)以单向链表的形式,为了最大化存储空间利用率,不同size(32B、64B、128B...)的记录被挂在不同的链表上,NamespaceDetail里的deletedListSmall/deletedListLarge包含指向这些不同大小链表头部的指针。

(3)整体文件结构图

Image Added