根本原因是底层文件系统行为、磁盘类型和i/o调度器差异;file.copy/move仅透传win32 api,物理层执行路径已分叉。

为什么 File.Copy 和 File.Move 在不同机器上耗时差异大
根本原因不是函数本身不可控,而是底层依赖的文件系统行为(如 NTFS 日志、缓存策略、卷影复制)、磁盘类型(HDD/SSD/NVMe)和 I/O 调度器不一致。C# 的 IO 类只是透传 Win32 API,CopyFileEx 或 MoveFileEx 的执行路径在物理层就分叉了。
实操建议:
- 避免在性能敏感路径中直接调用
File.Copy做大文件搬运;改用带缓冲的流式复制(FileStream+Span<byte></byte>),可显式控制每次读写大小(如 64KB),减少系统调用抖动 - 若必须用
File.Copy,加上copyOptions: FileOptions.None显式禁用默认的异步/缓存标记,防止某些 Windows 版本自动启用COPY_FILE_NO_BUFFERING导致 HDD 上反向变慢 - 不要依赖
Stopwatch测单次File.Copy耗时——冷盘缓存、AV 扫描、索引服务可能在任意时刻介入;应取连续 5 次操作的中位数,并排除首尾各一次
如何让 Directory.GetFiles 返回顺序稳定且可复现
Windows 文件系统不保证目录枚举顺序,NTFS 按 MFT 索引顺序返回,而 FAT32 可能按创建时间;.NET 的 Directory.GetFiles 默认不做排序,结果取决于底层驱动返回顺序。
实操建议:
- 永远对
Directory.GetFiles结果手动排序:files.OrderBy(p => p, StringComparer.Ordinal);用Ordinal而非CurrentCulture,避免区域设置影响 - 如果只关心文件名而非全路径,提取
Path.GetFileName后再排序,减少字符串比较开销 - 注意
SearchOption.AllDirectories下子目录遍历顺序也不确定,需对每层结果分别排序,不能只排最终扁平列表
FileStream 构造参数怎么选才不容易掉进性能陷阱
关键参数是 bufferSize、useAsync 和 FileOptions 组合,三者互相影响。比如设了 useAsync: true 却用小 buffer(如 4KB),会触发大量细碎的 I/O 请求,反而比同步模式更慢。
实操建议:
- 读大文件(>1MB):bufferSize 设为 64KB–1MB,
useAsync: true,FileOptions.SequentialScan(提示系统预读) - 写日志类小文件:bufferSize 保持默认 4KB,
useAsync: false,加FileOptions.WriteThrough | FileOptions.NoBuffering避免数据滞留在系统缓存中(但注意:NoBuffering 要求 buffer 对齐且大小是扇区倍数) - 绝对不要在循环里反复 new
FileStream处理同一文件——句柄分配/释放开销远超 IO 本身;复用FileStream实例,或用using+FileShare.ReadWrite控制并发
为什么 File.GetLastWriteTimeUtc 在某些 NAS 或云盘上返回不准
不是 .NET 的问题,而是 SMB/CIFS 协议或对象存储网关对时间戳精度的支持不一。例如某些 SMB 服务器只保留到 2 秒粒度,或强制四舍五入到 FAT32 的 2 秒精度;OneDrive/SharePoint Online 甚至会重写时间戳为上传完成时间。
实操建议:
- 不要用
File.GetLastWriteTimeUtc做精确依赖判断(如“是否新于某时间点”);改用文件哈希(SHA256)或自定义元数据(如写入.version文件)做变更识别 - 若必须依赖时间戳,先检查目标路径是否本地 NTFS:
new DriveInfo(Path.GetPathRoot(path)).DriveFormat == "NTFS";非 NTFS 环境降级为仅用于粗略排序 - 跨平台部署时,统一用
File.GetCreationTimeUtc替代——它在大多数网络文件系统中反而更稳定(因为创建后基本不更新)
真正难控的从来不是代码行,而是你没看见的那层抽象:文件系统驱动、存储固件、网络协议栈。IO Determinism 不是靠换函数实现的,是靠主动放弃对“顺序”“时间”“速度”的绝对信任,转而用可验证的副本来锚定行为。











