内存映射文件

基本思想

进程通过系统调用将一个文件或文件的部分映射到进程的虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是对文件进行读写。

实现

在多数实现中,在影射共享的页面时不会涉及读入页面的内容,而是在访问页面时页面才会被每次一页读入,磁盘文件则被当做后备存储。

分类

内存映射文件主要有两个分类:持久化(Persisted)和非持久化(Non-persisted)

持久化

持久化文件时,当进程退出或显示地解除文件映射时,所有修改页面会写回映射的文件中。

非持久化

非持久化时,文件并不关联硬盘文件。当关闭内存映射文件时,所有数据被抛弃。非持久化适用于进程通信的共享内存。

在Windows操作系统中,不需要调用CreateFile。调用CreateFileMapping时,将INVALID_HANDLE_VALUE作为hFile参数传入,这表示创建的文件映射不是磁盘文件,而是页交换文件。

图例

内存映射文件

应用场景

大文件读写

如果是小文件,那么内存映射文件会导致碎片空间浪费,因为内存映射以页为单位加载数据,一般内存中一页通常至少要4KB。因此一个5KB的文件需要映射到内存需要两页,也就是8KB,从而浪费了3KB内存空间。

操作系统加载进程

多进程共享内存

Java对内存映射的支持

FileChannel
代码示例:

public class DiskHelper {
    private final String fileName;
    private final long fileSize;
    private FileChannel fileChannel;
    private MappedByteBuffer mappedByteBuffer;

    public DiskHelper(final String fileName, final long fileSize) throws IOException {
        this.fileName = fileName;
        this.fileSize = fileSize;

        // 内存映射
        File file = new File(fileName);
        try {
            this.fileChannel = new RandomAccessFile(file, "rw").getChannel();
            this.mappedByteBuffer = this.fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileChannel != null) {
                this.fileChannel.close();
            }

        }
    }

    public byte[] read(long offset, int length) {
        ByteBuffer buffer = mappedByteBuffer.slice();
        buffer.position((int) offset);
        byte[] bytes = new byte[length];
        buffer.get(bytes);
        buffer.clear();
        return bytes;
    }

    public void write(byte[] bytes, long offset) {
        ByteBuffer buffer = mappedByteBuffer.slice();
        buffer.position((int) offset);
        buffer.put(bytes);
        mappedByteBuffer.force();
    }

    public void write(byte[] bytes, int offset, int length) {
        ByteBuffer buffer = mappedByteBuffer.slice();
        buffer.position(offset);
        buffer.put(bytes, 0, length);
        mappedByteBuffer.force();
    }

    public void shutdown() {
        try {
            if (fileChannel != null) {
                fileChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试用例

public class DiskHelperTest {

    @Test
    public void testCreateReadWrite() throws IOException {
        DiskHelper diskHelper = new DiskHelper("test", 1024L * 1024);
        String test = "abcd";
        diskHelper.write(test.getBytes(StandardCharsets.UTF_8), 0L);

        byte[] content = diskHelper.read(0, 4);
        System.out.println(new String(content, StandardCharsets.UTF_8));
        diskHelper.shutdown();
    }
}

参考

  1. https://zh.wikipedia.org/wiki/%E5%86%85%E5%AD%98%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6
  2. 操作系统原理-内存映射文件 陈向群
上一篇:LFU缓存算法及Java实现


下一篇:ThreadPoolExecutor使用测试3-测试在全部任务终止后,再次向线程池提交任务