memorymappedfile 不是大文本文件的默认解法,顺序读应优先用 streamreader + bufferedstream;必须显式设 capacity 分块映射,用 memorymappedviewaccessor 跳读,避免全量映射触发页错误风暴、虚拟地址耗尽及越界访问。

MemoryMappedFile 不是大文本文件的默认解法,顺序读就别硬上——它卡死、吃虚拟地址、还容易越界。
MemoryMappedFile.CreateFromFile 一读就卡死?
不是代码写错了,是默认行为在“骗你”:调用 MemoryMappedFile.CreateFromFile 且没指定 capacity 时,.NET 会尝试把整个文件映射进虚拟地址空间。虽然物理内存不会立刻分配,但一旦你用 Span<byte>.GetPinnableReference()</byte> 或 UnsafeAccessor 扫一遍数据(比如逐行解析),就会触发海量页错误 + 磁盘 I/O,界面直接冻结。
真正该做的只有三件事:
- 显式传入
capacity(例如64 * 1024 * 1024),控制单次映射大小 - 用
MemoryMappedViewAccessor的Offset和Size跳着读,别碰MemoryMappedViewStream - 如果只是从头到尾读 .txt,老老实实用
StreamReader+BufferedStream,它更稳、更快、不占虚拟地址
想用 MemoryMappedFile 读 .txt,怎么分块跳着读?
文本文件没有固定长度记录,不能像二进制那样按偏移直接切。你得自己实现“找换行符”的逻辑,否则会切在半行中间。
关键点:
- 每次
CreateViewAccessor(offset, size)前,确保offset + size不超文件长度,且size≤ 你设的capacity - 读出的
byte[]可能以半个 UTF-8 字符结尾,必须检查末尾是否为不完整编码(Encoding.UTF8.GetCharCount()会报错) - 跨块边界处要保留最后若干字节(比如 4 字节),和下一块开头拼起来再找
\n,否则丢行 - 别用
Encoding.GetString(byte[], offset, count)直接转——它不处理截断,推荐用ReadOnlySpan<byte>.Utf8ToUtf16</byte>+ReadOnlyMemory<char></char>流式解码
两个进程共享同一份大文本,怎么避免读到半截内容?
MemoryMappedFile 本身不保证原子性,也不管你读到哪了。它只是给你一块“裸内存”,读写全靠自觉。
必须加同步机制:
- 命名要用
"Global\MyLogData",否则 Windows 默认在会话命名空间里找,服务进程和桌面进程根本看不到彼此 - 共享区只放原始字节,结构体必须加
[StructLayout(LayoutKind.Sequential, Pack = 1)],字段用[MarshalAs]显式声明大小 - 加一个
EventWaitHandle控制状态:写进程写完有效数据后调event.Set();读进程先event.WaitOne(),再读,读完清空或推进指针 - 不要让读写双方共用同一个
MemoryMappedViewAccessor实例——它非线程安全,且底层SafeMemoryMappedViewHandle可能被 GC 回收
映射 10GB 文件时提示 “Not enough storage is available”
这不是磁盘空间不足,是 32 位进程用户模式虚拟地址空间耗尽(上限约 2GB)。哪怕你只映射 64MB,只要多次创建/释放视图,碎片化严重,也可能失败。
解决路径很窄:
- 必须用 64 位进程(
PlatformTarget = x64),这是硬门槛 - 避免频繁
CreateViewAccessor→Dispose,尽量复用视图实例(但注意线程安全) - 不用
MemoryMappedViewStream,它内部有缓冲层,额外拷贝 + 隐藏生命周期管理,极易引发句柄泄漏 - 映射前检查
accessor.SafeMemoryMappedViewHandle.IsInvalid,越界访问报的 “protected memory” 其实就是这个句柄已失效
真正难的从来不是“怎么映射”,而是“怎么在不破坏文本语义的前提下,把随机访问能力用对地方”。多数场景下,你并不需要它——除非你在做日志实时 tail + 多进程联合分析,或者文本本身就是结构化二进制混合体。










