MemoryMappedFile 与 MemoryMappedViewAccessor

MemoryMappedFile 类

在 .NET 8 里,System.IO.MemoryMappedFiles.MemoryMappedFile 类可创建和操作内存映射文件

内存映射文件是将磁盘文件的内容映射到进程虚拟地址空间的一种机制,如此一来,文件的内容就能像访问内存一样进行访问,而不用借助传统的文件 I/O 操作(例如 FileStream)

作用

高性能 I/O 操作
通过内存映射文件,我们可以直接在内存中对文件内容进行操作,这样就避免了频繁的磁盘 I/O 操作,极大地提升了数据读写的性能。这在处理大型文件时尤为关键。

进程间通信(IPC)

多个进程能够同时映射同一个内存映射文件,从而实现进程间的数据共享与通信。
不同进程可以对共享的内存区域进行读写操作,进而交换数据。

内存管理

操作系统会自动管理内存映射文件的内存,保证数据在内存和磁盘之间高效地交换。
当物理内存不足时,操作系统会将部分数据交换到磁盘上;而当需要访问这些数据时,再将其加载到内存中。

使用场景

处理大型文件

当需要处理大型文件(如数据库文件、图像文件等)时,使用 MemoryMappedFile 可以显著提升读写性能。
由于数据直接映射到内存,无需频繁地从磁盘读取或写入数据,能加快数据处理速度。

进程间通信

在多进程应用程序中,不同进程可以通过共享内存映射文件来交换数据。
例如,一个生产者进程负责将数据写入内存映射文件,而一个或多个消费者进程则从该文件中读取数据。

实时数据处理

在实时系统中,需要快速访问和处理大量数据。内存映射文件可以提供高效的数据访问方式,满足实时性要求。

MemoryMappedViewAccessor 类

MemoryMappedViewAccessor 类是 System.IO.MemoryMappedFiles 命名空间下的一个重要类,它在 .NET 中用于提供对内存映射文件的随机访问功能。

功能

MemoryMappedViewAccessor 类允许我们像操作数组一样直接对内存映射文件的内容进行读写,提供了一系列的方法来读写不同类型的数据,比如整数、浮点数、字节等。借助该类,我们能够随机访问内存映射文件中的任何位置,而不局限于顺序访问,这在处理大型文件或者需要随机访问数据的场景中非常实用。

常用属性

Capacity:获取内存映射视图的大小(以字节为单位)。

IsClosed:获取一个值,该值指示当前的 MemoryMappedViewAccessor 是否已关闭。

IsDisposed:获取一个值,该值指示当前的 MemoryMappedViewAccessor 是否已释放。

常用方法

Read<T>(long position):从内存映射视图的指定位置读取一个值,T 可以是各种基本数据类型,如 int、long、float 等。

ReadArray<T>(long position, T[] array, int offset, int count):从内存映射视图的指定位置开始,将指定数量的元素读取到数组中。

Write(long position, T value):将一个值写入到内存映射视图的指定位置。

WriteArray<T>(long position, T[] array, int offset, int count):从指定的数组中将指定数量的元素写入到内存映射视图的指定位置。

Flush():将自上次调用 Flush 以来对内存映射视图所做的所有更改写入基础文件。

如何使用 MemoryMappedFile 与 MemoryMappedViewAccessor 进行文件读写操作

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

class Program
{
    static void Main()
    {
        string filePath = "data.bin";
        long fileSize = 1024; // 1KB

        // 创建或打开一个内存映射文件,大小为 1KB
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, null, fileSize))
        {
            // 创建一个 MemoryMappedViewAccessor 来访问内存映射文件
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                // 写入一个整数到内存映射文件的指定位置
                int valueToWrite = 12345;
                long position = 0;
                accessor.Write(position, valueToWrite);

                // 从内存映射文件的指定位置读取整数
                int readValue = accessor.ReadInt32(position);
                Console.WriteLine($"Read value: {readValue}");

                // 写入一个整数数组到内存映射文件
                int[] intArray = { 1, 2, 3, 4, 5 };
                position = 4; // 从第 4 个字节开始写入
                accessor.WriteArray(position, intArray, 0, intArray.Length);

                // 从内存映射文件读取整数数组
                int[] readArray = new int[intArray.Length];
                accessor.ReadArray(position, readArray, 0, readArray.Length);
                Console.Write("Read array: ");
                foreach (int num in readArray)
                {
                    Console.Write(num + " ");
                }
                Console.WriteLine();
            }
        }
    }
}

写入字符串

要写入字符串,需要先把字符串转换为字节数组,因为 MemoryMappedViewAccessor 主要处理字节数据。在读取时,再把字节数组转换回字符串。
以下是一个示例代码,展示了如何在内存映射文件中写入和读取字符串:

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

class Program
{
    static void Main()
    {
        string filePath = "string_data.bin";
        long fileSize = 1024; // 1KB

        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, null, fileSize))
        {
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                // 要写入的字符串
                string textToWrite = "Hello, Memory Mapped File!";
                // 把字符串转换为 UTF - 8 编码的字节数组。
                byte[] textBytes = Encoding.UTF8.GetBytes(textToWrite);

                // 写入字符串的长度
                accessor.Write(0, textBytes.Length);

                // 从偏移量为 4 的位置,开始写入字符串的字节数据
                accessor.WriteArray(4, textBytes, 0, textBytes.Length);

                // 从偏移量为 0 的位置,读取字符串的长度
                int length = accessor.ReadInt32(0);

                // 读取字符串的字节数据
                byte[] readBytes = new byte[length];

                // 根据读取到的长度,从偏移量为 4 的位置读取相应数量的字节
                accessor.ReadArray(4, readBytes, 0, length);

                // 将字节数据转换为字符串
                string readText = Encoding.UTF8.GetString(readBytes);
                Console.WriteLine($"Read string: {readText}");
            }
        }
    }
}

写入对象

若要写入对象,需要先把对象序列化,也就是将对象转换为字节流,然后再写入内存映射文件。读取时则进行反序列化操作,将字节流转换回对象。

以下是一个示例代码,展示了如何在内存映射文件中写入和读取自定义对象:

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        string filePath = "object_data.bin";
        long fileSize = 1024; // 1KB

        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, null, fileSize))
        {
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                // 创建一个 Person 对象
                Person personToWrite = new Person { Name = "John", Age = 30 };

                // 序列化对象
                BinaryFormatter formatter = new BinaryFormatter();
                using (MemoryStream ms = new MemoryStream())
                {
                    formatter.Serialize(ms, personToWrite);
                    byte[] objectBytes = ms.ToArray();

                    // 写入对象的长度
                    accessor.Write(0, objectBytes.Length);

                    // 从偏移量为 4 的位置,开始写入对象的字节数据
                    accessor.WriteArray(4, objectBytes, 0, objectBytes.Length);
                }

                // 读取对象的长度
                int length = accessor.ReadInt32(0);

                // 读取对象的字节数据
                byte[] readBytes = new byte[length];
                accessor.ReadArray(4, readBytes, 0, length);

                // 反序列化对象
                using (MemoryStream ms = new MemoryStream(readBytes))
                {
                    Person readPerson = (Person)formatter.Deserialize(ms);
                    Console.WriteLine($"Read Person: Name = {readPerson.Name}, Age = {readPerson.Age}");
                }
            }
        }
    }
}

数字类型

MemoryMappedViewAccessor 提供了一系列专门用于写入基本数字类型的方法,例如 WriteInt32、WriteInt64、WriteSingle(用于 float 类型)、WriteDouble 等。当使用这些方法时,会自动处理数字到字节的转换,我们无需手动进行转换

以下是一个简单示例,展示使用这些方法写入不同数字类型:

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

class Program
{
    static void Main()
    {
        string filePath = "number_data.bin";
        long fileSize = 1024;

        using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, null, fileSize))
        {
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                int intValue = 123;
                long longValue = 456789L;
                float floatValue = 3.14f;
                double doubleValue = 2.71828;

                // 写入不同类型的数字
                accessor.Write(0, intValue);
                accessor.Write(4, longValue);
                accessor.Write(12, floatValue);
                accessor.Write(16, doubleValue);

                // 读取并验证
                int readInt = accessor.ReadInt32(0);
                long readLong = accessor.ReadInt64(4);
                float readFloat = accessor.ReadSingle(12);
                double readDouble = accessor.ReadDouble(16);

                Console.WriteLine($"Read int: {readInt}");
                Console.WriteLine($"Read long: {readLong}");
                Console.WriteLine($"Read float: {readFloat}");
                Console.WriteLine($"Read double: {readDouble}");
            }
        }
    }
}

在这个例子中,Write 方法会自动将不同的数字类型转换为字节序列并写入到内存映射文件中,Read 方法则负责将字节序列转换回相应的数字类型。

总之,对于基本数字类型,MemoryMappedViewAccessor 提供了方便的方法来自动处理字节转换;而对于字符串和对象,你需要手动进行转换为字节数组的操作。

posted @ 2025-04-24 17:24  青云Zeo  阅读(89)  评论(0)    收藏  举报