核心是比对ModTime(),但需用After()或截断到秒避免纳秒抖动误判;fsnotify易丢事件,不能替代时间戳校验;备份前须检查路径可写、文件类型及磁盘空间;替换旧文件须原子写入。

如何用 os.Stat 判断文件是否更新过
核心是比对修改时间戳(ModTime()),但要注意:Windows 和 Linux 对 nanosecond 精度处理不一致,time.Equal() 可能误判。实际应使用 time.Before() 或计算秒级差值。
- 直接用
fi.ModTime().After(lastBackupTime)更可靠,避免纳秒级抖动导致漏同步 - 若需支持 FAT32 等低精度文件系统,建议统一截断到秒:
fi.ModTime().Truncate(time.Second) - 注意
os.Stat会触发系统调用,频繁调用影响性能;批量扫描时可考虑filepath.Walk+ 缓存 stat 结果
为什么 fsnotify 不适合做可靠备份触发器
fsnotify 是事件驱动,但容易丢事件(尤其大量小文件写入、重命名、编辑器临时文件覆盖等场景),不能替代时间戳校验。
- 编辑器如 VS Code 保存时可能先写
.filename.swp再 rename,fsnotify只捕获 rename,但源文件时间戳已变 - rsync / cp 等工具批量复制时,事件可能被合并或丢失,而时间戳始终可查
- 正确做法是:用
fsnotify做“快速响应提示”,但最终同步决策仍以ModTime()为准
备份前必须检查的三个硬性条件
跳过这些检查,备份可能覆盖新数据或静默失败。
- 目标路径是否存在且可写?用
os.Stat(dstPath)+os.IsNotExist(err)判断,别只靠os.MkdirAll的返回值 - 源文件是否仍是 regular file?
fi.Mode().IsRegular()必须为 true,否则跳过符号链接、设备文件等 - 磁盘剩余空间是否足够?用
syscall.Statfs获取Avail字段,预留至少 10% 缓冲,避免ENOSPC中断备份
增量同步时如何安全替换旧文件
直接 os.Rename 或 os.WriteFile 覆盖有风险:进程崩溃会导致目标损坏。必须用原子写入。
立即学习“go语言免费学习笔记(深入)”;
- 先写临时文件:
tmpPath := dstPath + ".tmp",写完再os.Rename(tmpPath, dstPath) - 务必设置权限同源:
os.Chmod(tmpPath, fi.Mode()),否则 umask 可能导致权限丢失 - Linux 下
os.Rename是原子的;Windows 需用syscall.MoveFileEx并传MOVEFILE_REPLACE_EXISTING标志
时间戳同步本身不复杂,难点在边界情况——比如 NFS 挂载点时钟漂移、ext4 的 atime 更新策略、或者 backup 目录被用户手动 touch 过。这些不会报错,但会让 ModTime() 失效,得靠日志里打上 source/dest 的完整时间戳来事后排查。


















