必须检查os.WriteFile返回的error,否则可能误判写入成功;还需校验file.Write的n值防截断,并检查file.Close()和file.Sync()的error以确保落盘。

写入后不检查 os.WriteFile 返回的 error 就认为成功?危险
Go 中绝大多数 I/O 操作不会自动 panic,而是通过返回 error 显式告知失败。比如 os.WriteFile、file.Write 或 fmt.Fprintln,只要返回非 nil 的 error,就代表写入未按预期完成——哪怕文件已存在、路径可写、磁盘有空间,仍可能因权限、挂载状态、inode 耗尽或 NFS 临时断连而失败。
常见误判场景:
– 调用 os.WriteFile("config.json", data, 0644) 后没接 if err != nil 判断;
– 用 defer file.Close() 却忽略 file.Close() 自身也可能返回 error(缓冲区 flush 失败);
– 把 len(data) 和写入字节数混为一谈,以为“没报错=全写进去了”。
os.WriteFile 的 error 必须显式处理,且不能只看 nil
os.WriteFile 是原子写入封装,内部会创建临时文件、写入、重命名,失败时保证原文件不受影响。但它返回的 error 类型多样,需区分对待:
-
os.IsNotExist(err):父目录不存在,不是文件不存在——os.WriteFile本身不要求目标文件预先存在 -
os.IsPermission(err):对目录无写权限,或目标路径是只读文件系统 -
syscall.ENOSPC:磁盘空间不足(注意:需类型断言到*os.PathError或syscall.Errno) -
err == syscall.EIO:底层 I/O 错误,如坏块、USB 设备拔出
示例:
err := os.WriteFile("log.txt", []byte("hello"), 0644)
if err != nil {
if os.IsPermission(err) {
log.Fatal("no write permission to directory")
}
if errors.Is(err, syscall.ENOSPC) {
log.Fatal("disk full")
}
log.Fatal("write failed:", err)
}
用 file.Write + file.Close 时,两个 error 都要检查
当需要流式写入(比如大文件分块、日志追加),通常用 os.OpenFile 获取 *os.File,再调用 Write。此时必须检查两个地方:
-
Write返回的n int和error:即使err == nil,n 也表示只写入了部分数据(尤其在非阻塞或管道场景) -
Close返回的error:Linux 下 close(2) 可能因缓存回写失败而返回错误,Go 标准库会暴露该错误
正确模式:
f, err := os.OpenFile("data.bin", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
defer func() {
if cerr := f.Close(); cerr != nil {
log.Printf("close error: %v", cerr) // 不 fatal,但需记录
}
}()
n, err := f.Write(buf)
if err != nil {
log.Fatal("write error:", err)
}
if n != len(buf) {
log.Fatal("short write:", n, "/", len(buf))
}
sync.File.Sync() 不是默认行为,关键数据要主动刷盘
写入成功 ≠ 数据落盘。操作系统会缓存写入,崩溃或断电可能导致数据丢失。os.WriteFile 和 file.Write 默认都不同步。若需持久化保障(如数据库事务日志),必须显式调用 file.Sync():
-
Sync()成本高,每写一次都刷盘会严重拖慢性能 - 仅在真正需要强一致性时使用,比如写完配置后立刻重启服务依赖该配置
- 注意:Windows 下
Sync()行为与 Linux 略有差异,某些情况下可能不触发物理刷盘
示例:
err := os.WriteFile("critical.cfg", cfgBytes, 0644)
if err != nil {
log.Fatal(err)
}
f, _ := os.OpenFile("critical.cfg", os.O_RDWR, 0)
defer f.Close()
if err := f.Sync(); err != nil {
log.Fatal("sync failed:", err) // 此处失败意味着数据可能未落盘
}
最易被忽略的是:Close() 的 error 和 Sync() 的必要性。很多线上事故不是因为写不进去,而是写进去了却没刷到磁盘,或者 close 失败后程序还当一切正常。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











