c# 高级编程 22章516页 【文件和流】【内存映射的文件】

543 阅读3分钟

内存映射的文件

内存映射文件 允许:

  1. 访问文件
  2. 在不同的进程中 共享内存

使用场景

  • 使用 FileMap,快速 随机 访问大文件
  • 在不同的进程或任务之间 共享文件
  • 在不同的进程或任务之间 共享内存
  • 使用 访问器 直接从内存位置 进行读写
  • 使用 进行读写

内存映射文件API

  • 允许 使用 物理文件 或 共享内存
  • 共享的内存 可以大于 可用的 物理内存
    • 因此 需要一个 后备存储器
    • 把系统的页面文件 作为 后备存储器
  • 可以为 特定的 物理文件 或 共享内存 创建一个 内存映射文件
  • 创建了内存映射之后,就可以创建一个视图
    • 视图 用于 映射完整内存映射文件的一部分
    • 访问视图进行读写
  • 可以给 内存映射 指定名称
    • 使用名称,允许不同的进程访问 同一个 共享的内存

创建一个内存映射文件

  1. MemoryMappedFile.CreateOrOpen()
    • 第一个参数:指定内存映射的名称
    • 如果不存在就创建一个
    • 第二个参数:内存映射文件的大小
    • 第三个参数:所需的访问
  2. MemoryMappedFile.OpenExisting()
    • 打开现有文件
  3. MemoryMappedFile.CreateFromFile()
    • 访问物理文件

CreateViewAccessor() 从内存映射文件 创建一个 视图访问器 MemoryMappedViewAccessor

  • 第一个参数:使用的偏移量
  • 第二个参数:使用的大小
    • 最大值 可以是 内存映射文件的 大小
  • 第三个参数:指定用于读还是写
    • MemoryMappedFileAccess.Write

MemoryMappedViewAccessorWrite()方法,将内容 写入 共享内存

  • 第一个参数:数据应该写入的位置
  • 第二个参数:写入的内容

读取 共享内存里的内容

  1. MemoryMappedFile.OpenExisting()
    • 第一个参数:内存映射文件的名称
    • 第二个参数:读/写
  2. 从内存映射文件 创建一个 视图访问器MemoryMappedViewAccessor
  3. MemoryMappedViewAccessorRead()方法,读取 共享内存里的 内容
    • 第一个参数:数据的位置

示例

通过内存映射文件通信时,必须同步 读取器 和 写入器。这样读取器才知道数据何时可用。

        private ManualResetEventSlim _mapCreated = new ManualResetEventSlim(initialState: false);
        private ManualResetEventSlim _dataWrittenEvent = new ManualResetEventSlim(initialState: false);
        private const string MAPNAME = "SampleMap";
        
        public void Run()
        {
            Task.Run(() => WriterAsync());
            Task.Run(() => Reader());
            Console.WriteLine("tasks started");
        }
        private async Task WriterAsync()
        {
            try
            {
                using (MemoryMappedFile mappedFile = MemoryMappedFile.CreateOrOpen(MAPNAME, 10000, MemoryMappedFileAccess.ReadWrite))
                // MemoryMappedFile mappedFile = MemoryMappedFile.CreateFromFile("./memoryMappedFile", FileMode.Create, MAPNAME, 10000);
                {
                    _mapCreated.Set(); // signal shared memory segment created
                    Console.WriteLine("shared memory segment created");

                    using (MemoryMappedViewAccessor accessor = mappedFile.CreateViewAccessor(0, 10000, MemoryMappedFileAccess.Write))
                    {
                        for (int i = 0, pos = 0; i < 100; i++, pos += 4)
                        {
                            accessor.Write(pos, i);
                            Console.WriteLine($"written {i} at position {pos}");
                            await Task.Delay(10);
                        }
                        _dataWrittenEvent.Set(); // signal all data written
                        Console.WriteLine("data written");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"writer {ex.Message}");
            }
        }
        private void Reader()
        {
            try
            {
                Console.WriteLine("reader");
                _mapCreated.Wait();
                Console.WriteLine("reader starting");

                using (MemoryMappedFile mappedFile = MemoryMappedFile.OpenExisting(MAPNAME, MemoryMappedFileRights.Read))
                {
                    using (MemoryMappedViewAccessor accessor = mappedFile.CreateViewAccessor(0, 10000, MemoryMappedFileAccess.Read))
                    {
                        _dataWrittenEvent.Wait();
                        Console.WriteLine("reading can start now");

                        for (int i = 0; i < 400; i += 4)
                        {
                            int result = accessor.ReadInt32(i);
                            Console.WriteLine($"reading {result} from position {i}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"reader {ex.Message}");
            }
        }

使用流 MemoryMappedViewStream

  1. 可以从MemoryMappedFile 调用CreateViewStream()创建一个MemoryMappedViewStream
    • 其作用和用法类似于MemoryMappedViewAccessor
  2. MemoryMappedViewStream构造一个StreamWriter
  3. StreamWriterWriteLineAsync()方法将字符串写入到 流 里
    • StreamWriter会缓存写入操作,所以流的位置不是在每一个写入操作中都更新。而是只在写入块的时候 才更新
    • 为了让每个写入操作 流的位置 都更新,需要把StreamWriterAutoFlush属性设为true
  4. MemoryMappedViewStream构造一个StreamReader
  5. StreamReaderReadLineAsync()从共享内存中读取内容
        private async Task WriterUsingStreamsAsync()
        {
            try
            {
                using (MemoryMappedFile mappedFile = MemoryMappedFile.CreateOrOpen(MAPNAME, 10000, MemoryMappedFileAccess.ReadWrite))
                // MemoryMappedFile mappedFile = MemoryMappedFile.CreateFromFile("./memoryMappedFile", FileMode.Create, MAPNAME, 10000);
                {
                    _mapCreated.Set(); // signal shared memory segment created
                    Console.WriteLine("shared memory segment created");

                    MemoryMappedViewStream stream = mappedFile.CreateViewStream(0, 10000, MemoryMappedFileAccess.Write);
                    using (var writer = new StreamWriter(stream))
                    {
                        writer.AutoFlush = true;
                        for (int i = 0; i < 100; i++)
                        {
                            string s = $"some data {i}";
                            Console.WriteLine($"writing {s} at {stream.Position}");
                            await writer.WriteLineAsync(s);
                        }
                    }
                    _dataWrittenEvent.Set(); // signal all data written
                    Console.WriteLine("data written");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"writer {ex.Message}");
            }
        }
        private async Task ReaderUsingStreamsAsync()
        {
            try
            {
                Console.WriteLine("reader");
                _mapCreated.Wait();
                Console.WriteLine("reader starting");

                using (MemoryMappedFile mappedFile = MemoryMappedFile.OpenExisting(MAPNAME, MemoryMappedFileRights.Read))
                {
                    MemoryMappedViewStream stream = mappedFile.CreateViewStream(0, 10000, MemoryMappedFileAccess.Read);
                    using (var reader = new StreamReader(stream))
                    {
                        _dataWrittenEvent.Wait();
                        Console.WriteLine("reading can start now");

                        for (int i = 0; i < 100; i++)
                        {
                            long pos = stream.Position;
                            string s = await reader.ReadLineAsync();
                            Console.WriteLine($"read {s} from {pos}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"reader {ex.Message}");
            }
        }