应使用 FileInfo.LastWriteTimeUtc 比较文件修改时间,避免时区干扰;需结合文件大小与局部哈希(前/后4KB)校验,防止 FAT32 精度不足、内容变更但时间戳未更新等问题。

比对文件修改时间用 FileInfo.LastWriteTime,但要注意时区和精度问题
Windows 默认以本地时区存储 LastWriteTime,跨机器同步时如果一方是 UTC 时间(比如某些 Docker 容器或 CI 环境),直接比较会误判。它还只精确到 100 纳秒,但 NTFS 实际最小粒度是 100ns,FAT32 是 2 秒——这意味着 FAT32 上同一天内多次保存的文件,LastWriteTime 可能完全一样。
- 始终用
FileInfo.LastWriteTimeUtc做比较,避免时区干扰 - 不要单独依赖修改时间做同步决策,必须配合大小或哈希校验
- 测试时故意在 FAT32 U 盘上改文件,观察
LastWriteTime是否“卡住”不动
File.GetLastWriteTimeUtc() 和 FileInfo 的性能与异常处理差异
两者返回值一致,但底层行为不同:File.GetLastWriteTimeUtc() 是静态方法,每次调用都走完整路径解析 + 权限检查;FileInfo 实例化后可复用,且它的 LastWriteTimeUtc 属性是懒加载,首次访问才触发 IO。
- 批量处理上百个文件时,优先用
new FileInfo(path)+ 缓存实例,别反复调用File.GetLastWriteTimeUtc(path) -
File.GetLastWriteTimeUtc()在路径不存在时抛FileNotFoundException;FileInfo构造函数不会立即抛异常,但访问LastWriteTimeUtc时才会——这点容易漏掉 try/catch - 若只需判断“是否更新过”,用
FileInfo.Exists先筛一遍,避免对不存在路径反复触发异常
增量同步不能只看时间戳:三个必须补上的校验环节
仅靠 LastWriteTimeUtc 同步,会在这些场景出错:文件被还原(时间戳变旧但内容已更新)、编辑器先清空再写入(时间戳新但内容可能损坏)、硬链接或符号链接指向同一数据块(时间戳独立更新)。
- 第一步:确认目标文件存在且
LastWriteTimeUtc早于源文件 - 第二步:检查两文件
Length是否相等,不等直接复制(跳过哈希) - 第三步:长度相等时,用
SHA256计算前 4KB + 后 4KB(跳过中间大段零)快速比对,避免全量读取大文件
var hash = SHA256.Create(); hash.TransformBlock(sourceBytes, 0, 4096, null, 0); // 前 4KB hash.TransformFinalBlock(sourceBytes, sourceBytes.Length - 4096, 4096); // 后 4KB
WatchDirectory 不可靠,轮询间隔设成 1000ms 以上才实用
FileSystemWatcher 常被用来监听变更做实时同步,但它在高频率写入(如 VS 编译、Log 输出)下会丢事件,且 Changed 事件触发时文件可能还没写完(IOException 频发)。轮询虽土,反而稳定。
- 用
Task.Delay(1500)控制轮询节奏,避开系统定时器抖动 - 每次轮询只扫描变更过的目录层级,别每次都
Directory.GetFiles(root, "*", SearchOption.AllDirectories) - 维护一个内存字典
Dictionary<string, DateTime>存上次扫描各文件的时间戳,只比对变化过的路径
文件时间戳只是线索,不是证据。真正可靠的增量同步,永远建立在“时间戳初筛 + 大小快速过滤 + 局部哈希终判”三层结构上,少一层就容易静默出错。










