内存映射文件

内存映射文件

内存映射文件简介

内存映射文件(Memory-Mapped File)是一种将文件或其他对象映射到进程地址空间的机制,使得应用程序可以像访问内存一样访问文件内容。以下是内存映射文件的主要特点和工作原理:

主要特点

  1. 直接内存访问
    • 应用程序可以通过指针直接访问文件内容,而不需要通过传统的读写操作。这提高了文件访问的效率。
  2. 共享内存
    • 内存映射文件可以被多个进程共享,使得它们可以同时访问同一个文件。这在进程间通信(IPC)中非常有用。
  3. 延迟加载
    • 文件的内容并不是立即加载到内存中,而是在实际访问时才加载。这可以节省内存资源。
  4. 自动同步
    • 对内存映射区域的修改会自动反映到文件中,反之亦然。这意味着对内存的更改会直接影响到文件内容。

工作原理

  1. 创建内存映射文件

    • 操作系统提供的 API(如 Windows 的 CreateFileMappingMapViewOfFile)用于创建内存映射文件。
  2. 映射文件到内存

    • 通过将文件的某个部分映射到进程的虚拟地址空间,进程可以使用指针来访问文件内容。
  3. 访问和修改

    • 应用程序可以像访问普通数组一样访问映射的内存区域。对该区域的写入操作会直接修改文件内容。
  4. 解除映射

    • 当不再需要访问文件时,可以使用 API 解除映射,并释放相关资源。

优点

  1. **高性能**:由于数据直接在内存中访问,避免了频繁的磁盘I/O操作,因此读写速度非常快。
  2. **简单易用**:使用内存映射文件可以像操作普通内存一样操作文件内容,简化了代码逻辑。
  3. **节省内存**:只有实际访问的数据才会被加载到内存中,未访问的部分不会占用内存。
  4. **共享内存**:多个进程可以同时映射同一文件的不同部分,实现进程间的共享内存通信。

缺点

  1. **内存限制**:虽然内存映射文件可以映射大文件,但实际使用的物理内存仍然受限于系统的可用内存。
  2. **复杂性**:相对于传统的文件读写操作,内存映射文件的使用可能稍微复杂一些,需要理解虚拟内存和页面管理的概念。
  3. **数据一致性**:在多进程或多线程环境中,需要额外的同步机制来保证数据的一致性。

应用场景

  • 大文件处理:在处理大文件时,内存映射文件可以避免将整个文件加载到内存中。
  • 数据库:许多数据库系统使用内存映射文件来提高数据访问速度。
  • 进程间通信:通过共享内存映射文件,多个进程可以高效地交换数据。

示例

读取内存映射文件

using System;
using System.IO;
using System.IO.MemoryMappedFiles;

class Program
{
    static void Main()
    {
        string filePath = "path/to/your/file.txt";
        long fileSize = new FileInfo(filePath).Length;

        // 创建内存映射文件
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open))
        {
            // 创建视图访问器
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, fileSize))
            {
                // 读取文件内容
                byte[] buffer = new byte[fileSize];
                accessor.ReadArray(0, buffer, 0, buffer.Length);

                // 将字节数组转换为字符串
                string content = System.Text.Encoding.UTF8.GetString(buffer);
                Console.WriteLine(content);
            }
        }
    }
}

写入内存映射文件

using System;
using System.IO;
using System.IO.MemoryMappedFiles;

class Program
{
    static void Main()
    {
        string filePath = "path/to/your/file.txt";
        string content = "Hello, Memory-Mapped Files!";

        // 创建内存映射文件
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, maximumSize: (long)content.Length))
        {
            // 创建视图访问器
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, content.Length))
            {
                // 将字符串转换为字节数组
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(content);

                // 写入数据
                accessor.WriteArray(0, buffer, 0, buffer.Length);
            }
        }
    }
}

这段代码演示了如何创建和使用内存映射文件来存储和读取数据。

读取测试

这里我们分别使用IO的方式和 MMF方式来读取一个1G多的模型文件。IO方式耗时2000ms,MMF方式560ms

测试代码

[Button("TestReadByMemoryMappedFile")]
    void TestReadByMemoryMappedFile()
    {
        using (CustomTimer timer = new CustomTimer("ReadByMemoryMappedFile"))
        {
            ReadByMemoryMappedFile();
        }

        using (CustomTimer timer = new CustomTimer("ReadByIO"))
        {
            ReadByIO();
        }
    }

    private void ReadByIO()
    {
        string filePath = "E:\\A.ply";

        // 确保文件存在
        if (!System.IO.File.Exists(filePath))
        {
            Debug.Log("文件不存在: " + filePath);
            return;
        }

        // 读取文件内容
        byte[] buffer = System.IO.File.ReadAllBytes(filePath);

        // 将字节数组转换为字符串并输出
        string fileContent = System.Text.Encoding.UTF8.GetString(buffer);
        Debug.Log(fileContent.Length);
    }


    void ReadByMemoryMappedFile()
    {
        string filePath = "E:\\A.ply";

        // 确保文件存在
        if (!System.IO.File.Exists(filePath))
        {
            Debug.Log("文件不存在: " + filePath);
            return;
        }

        // 获取文件大小
        long fileSize = new System.IO.FileInfo(filePath).Length;

        // 创建内存映射文件
        using var mmf = System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(filePath, System.IO.FileMode.Open, "PlyMap", fileSize);
        // 创建视图流
        using var stream = mmf.CreateViewStream();
        // 读取文件内容
        byte[] buffer = new byte[fileSize];
        stream.Read(buffer, 0, buffer.Length);

        // 将字节数组转换为字符串并输出
        string fileContent = System.Text.Encoding.UTF8.GetString(buffer);
        Debug.Log(fileContent.Length);
    }

测试结果

内存映射文件读取文件

内存映射文件与传统IO方式的区别

1. 访问方式

  • 传统读取
    • 你需要通过文件操作函数(如 Open, Read, Write, Close)逐步读取文件。
    • 每次读取时,操作系统会从磁盘将数据加载到内存,过程较为繁琐。
  • 内存映射文件
    • 文件的内容被直接映射到进程的地址空间,你可以像访问普通数组一样访问文件内容。
    • 你只需简单地使用指针或索引来读取和修改数据。

2. 性能

  • 传统读取
    • 每次读取都涉及系统调用,开销较大,尤其是处理大文件时会频繁进行磁盘 I/O 操作,可能导致性能瓶颈。
  • 内存映射文件
    • 通过内存直接访问,减少了磁盘 I/O 的次数,通常能提供更好的性能,特别是在处理大文件或频繁读写时。

3. 内存管理

  • 传统读取
    • 你需要手动管理内存,读取的数据可能需要存储在一个数组或缓冲区中,使用完后需要释放。
  • 内存映射文件
    • 操作系统自动管理内存映射区域,内存不足时会自动将不常用的数据换出,保持高效的内存使用。

4. 共享和进程间通信

  • 传统读取
    • 如果多个进程需要访问同一文件,通常需要使用文件锁或其他机制来进行协调,复杂度较高。
  • 内存映射文件
    • 可以轻松实现多个进程共享同一个内存映射文件,进程间可以直接访问同一块内存区域,简化了进程间通信。

5. 延迟加载

  • 传统读取
    • 通常需要一次性读取整个文件或分块读取,可能浪费内存。
  • 内存映射文件
    • 只有在实际访问时,相关数据才会被加载到内存中,避免了不必要的内存占用。

读书

将内存映射文件和传统 I/O 比作读一本书,可以通过以下几个方面来理解它们之间的区别:

1. 获取方式

  • 内存映射文件
    • 就像把整本书的内容直接放在桌面上,你可以随时打开、查看任何一页,而不需要去书架上拿书。
    • 这种方式允许你直接访问内存中的数据,速度快。
  • 传统 I/O
    • 需要每次去书架上取书,查阅时逐页翻阅。
    • 每次读取内容都需要进行系统调用,速度较慢。

2. 访问方式

  • 内存映射文件
    • 你可以随机访问书中的任意一页,随时跳转到任何章节,效率高。
    • 内存中的数据可以被多个进程共享,方便快速访问。
  • 传统 I/O
    • 需要按顺序逐页查阅,不能快速跳转,效率低。
    • 每次读取都需要从磁盘加载数据,可能会造成延迟。

3. 性能

  • 内存映射文件
    • 由于数据已经在内存中,访问速度快,减少了磁盘 I/O 操作。
    • 适合处理大文件和需要频繁访问的场景。
  • 传统 I/O
    • 每次读取都涉及到磁盘操作,性能较低,尤其是在处理大文件时。
    • 适合小文件或简单的读取操作。

4. 资源管理

  • 内存映射文件
    • 操作系统管理内存映射,自动处理数据的加载和卸载。
    • 更高效地利用内存,减少了内存和磁盘之间的复制。
  • 传统 I/O
    • 程序员需要手动管理文件的打开、读取和关闭,容易出错。
    • 可能导致资源浪费,例如未及时关闭文件。

总结

内存映射文件

内存映射文件百科

内存映射文件视频

Microsoft MMP

posted @ 2024-11-21 09:34  世纪末の魔术师  阅读(18)  评论(0编辑  收藏  举报