os.openfile 的 flag 组合决定写入行为:覆盖用 o_create|o_wronly|o_trunc,追加用 o_create|o_wronly|o_append,缺后者会从头覆盖致数据残留;权限 0644 在 windows 无效;ioutil.writefile 已弃用,大文件或追加必须用 os.openfile + write + close/.sync。

os.OpenFile 用错模式就覆盖不了或追加失败
写文件时最常卡在 os.OpenFile 的 flag 参数上。它不像 os.Create 那样“默认覆盖”,也不像 Python 的 "a" 模式那样直白——flag 组合错了,要么报 permission denied,要么静默写空文件。
关键不是记全所有 flag,而是盯住三个组合:
-
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.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 才是安全追加;少个 O_APPEND,日志就可能被截断成半条记录。
0644 权限在不同系统表现不一致
Linux/macOS 下 0644 表示所有者可读写、组和其他人只读,但 Windows 忽略权限位,os.OpenFile 传进去的 mode 只影响是否设只读属性(且仅当 os.O_RDONLY 未设置时才生效)。更麻烦的是:如果父目录没写权限,哪怕 mode 设对了,也会在 open 阶段就报 permission denied。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 写入前用
os.Stat检查父目录是否存在且可写(os.IsPermission(err)判断权限问题) - 避免硬写
0644,改用0666 &^ umask(但 Go 标准库不暴露 umask,所以通常还是用0644,只是心里清楚它在 Windows 上无效) - 若需跨平台一致行为(比如配置文件必须可编辑),别依赖文件权限,改用运行时检查
os.IsWritable或直接尝试写一个临时文件
ioutil.WriteFile 已弃用,但很多人还在用
Go 1.16+ 中 ioutil.WriteFile 已移到 os.WriteFile,继续用旧包会触发 deprecated 警告,且无法控制 open flag(它内部固定用 O_CREATE|O_WRONLY|O_TRUNC,没法追加)。
替换要点:
- 把
ioutil.WriteFile(path, data, perm)改成os.WriteFile(path, data, perm) - 需要追加?不能用
os.WriteFile,必须回到os.OpenFile+Write流程 -
os.WriteFile是原子写入(先写临时文件再 rename),适合配置、JSON 等小文件;大文件或流式写入仍得用os.File手动控制
错误示范:os.WriteFile("data.bin", buf, 0644) 用于日志轮转?不行——它每次都会覆盖,不是追加。
写入后不 close 或不 sync,数据可能丢
Go 的 *os.File 是带缓冲的,调用 Write 后数据未必落地磁盘。尤其在程序崩溃、断电或容器被 kill 时,最后几 KB 日志/状态就没了。
该怎么做:
- 务必调用
file.Close()—— 它会 flush 缓冲并释放 fd,漏掉会导致 fd 泄露和数据丢失 - 对关键数据(如数据库 WAL、交易日志),在
Close()前加file.Sync(),强制刷盘(注意:性能下降明显,别滥用) - 用
defer file.Close()很方便,但别 defer 在函数开头就打开的 file——如果后续操作失败,close 还是会执行,但此时 file 可能是 nil 或无效
最易忽略的一点:os.WriteFile 内部已经 close 和 flush,不用额外处理;但凡自己用 os.OpenFile,close 就是你自己的事,没有例外。










