使用 fileoptions.nobuffering 可绕过 windows 系统缓存,但要求文件偏移、缓冲区地址和读写长度均按磁盘扇区大小(如 512 或 4096 字节)对齐,否则抛出 ioexception;需配合 fileoptions.writethrough 和 flushfilebuffers 才能确保数据持久落盘。

使用 FileOptions.NoBuffering 绕过系统缓存
Windows 下 C# 的 FileStream 支持绕过内核缓存,但必须满足严格对齐要求:文件偏移、缓冲区地址、读写长度三者都需是磁盘扇区大小(通常是 512 字节或 4096 字节)的整数倍。不满足时会直接抛出 IOException:“The parameter is incorrect.”
实操建议:
- 用
GetDiskFreeSpace或IOCTL_DISK_GET_LENGTH_INFO获取实际扇区大小,别硬编码 512 - 分配缓冲区必须用
Marshal.AllocHGlobal(保证地址对齐),或用ArrayPool<byte>.Shared.Rent</byte>+GC.AllocateArray<byte>(size, pinned: true)</byte>(.NET 6+) - 打开文件时传入
FileOptions.NoBuffering | FileOptions.WriteThrough,后者确保数据跳过设备驱动缓存直写磁盘 - 每次
Read/Write必须是扇区对齐长度;若只需读 1 字节,也得读满一个扇区再取所需字节
CreateFile 底层句柄配合 FILE_FLAG_NO_BUFFERING
当 FileStream 封装不够用(比如需要控制重试策略、自定义完成端口或映射到非托管内存),可调用 Win32 CreateFile 获取原生句柄,再用 SafeFileHandle 构造 FileStream。
关键点:
- 调用
CreateFile时dwFlagsAndAttributes参数必须包含FILE_FLAG_NO_BUFFERING(值为 0x20000000) - 即使走此路径,扇区对齐约束依然存在——不是“绕过缓存”就等于“任意读写”
- 注意
FILE_SHARE_READ/FILE_SHARE_WRITE设置,否则其他进程可能因共享冲突打不开同一文件 - 关闭句柄前务必调用
FlushFileBuffers(FileOptions.WriteThrough不自动触发此操作)
为什么 FileOptions.WriteThrough 单独不够?
FileOptions.WriteThrough 只跳过操作系统页缓存,但数据仍可能滞留在硬盘固件缓存或 SATA/PCIe 控制器缓存中。断电时这部分数据会丢失,无法保证持久性。
真正落盘需组合使用:
-
FileOptions.NoBuffering→ 跳过 OS 缓存 -
FileOptions.WriteThrough→ 禁用设备驱动缓存(部分控制器支持) - 手动调用
FlushFileBuffers→ 强制下刷到底层存储介质
缺任一环,都可能在崩溃后看到“写入成功但文件内容未更新”的现象。
Linux/macOS 上没有等效方案
.NET 的 FileOptions.NoBuffering 在非 Windows 平台被静默忽略,底层调用仍是 open() + O_DIRECT,但 .NET 运行时未暴露该标志,且 O_DIRECT 在 Linux 上有更复杂的对齐与生命周期要求(如缓冲区内存必须锁定、不能被 swap)。
若跨平台需求强烈:
- Windows 用
NoBuffering+ 扇区对齐 - Linux/macOS 改用
MemoryMappedFile+MapMode.ReadWrite+FlushAsync(依赖 fsync 行为) - 或直接 P/Invoke
open(… O_DIRECT)+read/write系统调用(需自行处理 errno、信号中断、对齐)
扇区对齐检查和跨平台 flush 逻辑容易漏掉,这是最常导致数据不一致的地方。










