os.OpenFile的flag组合决定覆盖或追加:O_CREATE|O_WRONLY|O_TRUNC为覆盖,O_CREATE|O_WRONLY|O_APPEND为追加,缺二者之一则危险覆盖;WriteFile仅支持覆盖;高频追加应复用文件句柄并缓冲。

os.OpenFile 的 flag 组合决定是覆盖还是追加
Go 没有独立的“覆盖写”或“追加写”函数,行为完全由 os.OpenFile 的 flag 参数决定。错一个标志,就可能静默覆盖、截断内容,或直接报错。
常见错误现象:
- 少写
os.O_APPEND,却以为在追加——实际从头开始写,旧文件后半段残留,日志只剩半行 - 漏掉
os.O_CREATE,文件不存在时直接返回"no such file or directory" - 用了
os.O_TRUNC但没配os.O_WRONLY,编译不报错,运行时 panic:invalid argument
标准组合如下:
-
os.O_CREATE | os.O_WRONLY | os.O_TRUNC:覆盖写入 —— 文件存在则清空,不存在则创建 -
os.O_CREATE | os.O_WRONLY | os.O_APPEND:追加写入 —— 光标自动移到末尾,不破坏原有数据 -
os.O_CREATE | os.O_WRONLY(缺O_TRUNC和O_APPEND):最危险 —— 写多少字节就覆盖开头多少字节,后面全保留
os.WriteFile 只能覆盖,不能追加
os.WriteFile 内部固定使用 os.O_CREATE | os.O_WRONLY | os.O_TRUNC,每次调用都会重写整个文件。它不适合日志、拼接、多 goroutine 场景。
立即学习“go语言免费学习笔记(深入)”;
容易踩的坑:
- 想用
os.WriteFile实现“追加”,只能先读全文件、拼字符串、再全量写回 —— 但两次 syscall 之间存在竞态窗口,别人可能已写入新内容 - 并发调用
os.WriteFile到同一文件,后者必然覆盖前者,丢数据 - 大文件反复读+写+分配内存,性能差,还容易 OOM
适用场景仅限于「一次性写入完整快照」,比如保存配置、导出报告。
高频追加要避免反复打开关闭文件
Go 1.16+ 提供了 os.AppendFile,一行搞定追加,但它每次调用都执行 open → write → close 流程。对每秒写几十次的日志来说,开销明显。
实操建议:
- 复用
*os.File句柄:用os.OpenFile打开一次,长期持有,程序退出前才Close() - 若写入频繁且单次内容小,加
bufio.NewWriter(f)缓冲,但必须在关闭前显式调用bufWriter.Flush(),否则最后一批内容滞留内存 - 并发写同一文件时,
WriteString不是原子操作 —— 多个 goroutine 直接往同一个*os.File写,可能交错或截断;应加sync.Mutex,或改用log.New(f, "")(内部已加锁)
0644 权限和换行符在跨平台时容易被忽略
0644 在 Linux/macOS 下表示所有者可读写、组和其他人只读,但 Windows 忽略权限位,os.OpenFile 传进去的 mode 只影响是否设只读属性(且仅当未设置 os.O_RDONLY 时才生效)。
更麻烦的是:如果父目录没写权限,哪怕 mode 设对了,也会在 open 阶段就报 "permission denied"。
Windows 下还要注意换行符:\r\n 是默认,而 Unix 系统用 \n。若日志需跨平台解析,别依赖系统默认,显式控制换行符。
真正容易被忽略的点是:写入前不检查父目录是否存在且可写 —— 应该用 os.Stat + os.IsPermission(err) 主动判断,而不是等 OpenFile 失败再处理。


















