C#创建内存映射视图 C#如何映射文件的部分区域到内存

来源:这里教程网 时间:2026-02-21 17:42:20 作者:

MemoryMappedFile.CreateFromFile 为什么不能直接映射部分区域

因为

CreateFromFile
默认创建的是「完整文件映射」,底层调用 Windows 的
CreateFileMapping
时未指定
dwMaximumSizeHigh
/
dwMaximumSizeLow
和偏移参数,所以无法控制起始位置和长度。真正支持部分映射的是
CreateFromFile
的重载或更底层的
MemoryMappedFile.Create
+
CreateViewAccessor
组合。

如何用 CreateViewAccessor 映射文件的指定字节范围

必须先创建内存映射对象(可读/写/复制),再通过

CreateViewAccessor
指定偏移与大小。关键点在于:视图的偏移必须是系统页面大小(通常是 4096)的整数倍,否则抛出
ArgumentException
;长度可以任意,但超出文件实际大小会触发访问异常。

offset
参数必须对齐到
Environment.SystemPageSize
,例如想从第 8192 字节开始映射,没问题;但从 8200 开始就会报错
capacity
是视图可见长度,不是必须等于剩余文件长度,但读写越界会引发
IOException
AccessViolationException
若文件以
FileMode.Append
打开或未预分配空间,映射后写入超出当前文件长度的位置,需先调用
FileStream.SetLength
扩容,否则写操作失败
using var fs = new FileStream("data.bin", FileMode.Open, FileAccess.ReadWrite);
using var mmf = MemoryMappedFile.CreateFromFile(fs, null, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false);
// 从第 16KB 处开始,映射 4KB 视图(offset=16384 必须是 4096 的倍数)
using var accessor = mmf.CreateViewAccessor(16384, 4096, MemoryMappedFileAccess.ReadWrite);
accessor.Write(0, (byte)42); // 写入视图起始位置

映射只读区域时要注意 FileAccess 和 MemoryMappedFileAccess 的匹配

如果原始

FileStream
是只读打开的,即使
MemoryMappedFileAccess
设为
ReadWrite
CreateViewAccessor
仍会成功,但后续写操作会抛出
UnauthorizedAccessException
。Windows 层面的访问权限由底层句柄决定,上层设置无效。

确保
FileStream
FileAccess
与后续
MemoryMappedFileAccess
一致,尤其在只读场景下不要误传
ReadWrite
若需多个进程共享只读视图,建议显式使用
FileAccess.Read
+
MemoryMappedFileAccess.Read
,避免运行时权限冲突
映射后修改文件长度(如
SetLength
)在只读流下会直接失败,必须用可写流打开

大文件分块映射时为何要避免频繁创建/释放 ViewAccessor

每次调用

CreateViewAccessor
都会触发一次
MapViewOfFileEx
系统调用,并占用一个虚拟内存区域。在 32 位进程或内存紧张环境下,频繁映射不同区域可能导致
OutOfMemoryException
,即使物理内存充足——因为虚拟地址空间碎片化。

优先复用同一个
MemoryMappedFile
实例,按需切换
ViewAccessor
,而不是为每段都新建
MemoryMappedFile
若需同时访问多个不重叠区域,可创建多个
ViewAccessor
,但务必及时
Dispose
,否则虚拟内存泄漏比托管内存更隐蔽
注意
ViewAccessor
不是线程安全的,多线程并发读写同一视图需自行加锁,或为每个线程分配独立视图
实际用的时候,最容易被忽略的是 offset 对齐和文件长度预分配——这两点不满足,程序可能在开发机跑得好好的,一到客户环境就崩溃,而且错误信息往往只显示“尝试读取或写入受保护的内存”,根本看不出是映射参数问题。

相关推荐