Go文件操作需注意五点:open/close必须成对;读大文件须流式处理;写文件慎用os.WriteFile;路径用filepath.Join、编码需处理BOM;多goroutine写需加锁。

open 和 close 必须成对出现,否则文件句柄泄漏
Go 中用 os.Open 或 os.OpenFile 打开文件后,必须显式调用 Close(),Go 不会自动回收。漏掉 Close() 在长期运行服务中会导致 too many open files 错误。
- 优先用
defer f.Close(),但注意它在函数返回时才执行,若函数体很长或有多个return,仍可能因 panic 未执行到 defer —— 更稳妥的是用if err != nil { f.Close(); return err }配合提前返回 -
os.Open只支持只读;写入或追加必须用os.OpenFile并传入正确 flag,例如os.O_WRONLY|os.O_CREATE|os.O_TRUNC - 多个 goroutine 同时写同一文件需自行加锁,
*os.File本身不是并发安全的
读大文件别直接 ioutil.ReadAll,改用 bufio.Scanner 或 io.Copy
ioutil.ReadAll(Go 1.16+ 已移至 io.ReadAll)会把整个文件一次性加载进内存,几 GB 的日志文件极易触发 OOM。真实场景应按需流式处理。
- 逐行读:用
bufio.Scanner,默认单行上限 64KB,超长行会报scanner: token too long,可调ScanBytes或Bufio.NewReader+ReadString('\n') - 逐块读:用
io.Copy(如复制文件)或手动buf := make([]byte, 32*1024); n, _ := f.Read(buf) - JSON / CSV 等结构化数据,直接用
json.NewDecoder(f)或csv.NewReader(f),它们内部已做缓冲和流式解析
写文件时 os.WriteFile 是便捷但有坑的封装
os.WriteFile 看似简单:传路径、字节切片、权限即可。但它底层是先写临时文件再 rename,且**不支持追加**,每次调用都会覆盖全量内容。
- 日志追加场景必须用
os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)+f.Write -
os.WriteFile的权限参数在 Windows 下被忽略,实际由系统 ACL 控制;Linux/macOS 上若传0600却期望组用户可读,会出意料之外的权限问题 - 写关键配置文件时,
os.WriteFile的原子性依赖rename,而某些网络文件系统(如 NFSv3)不保证 rename 原子,此时应手写“写临时文件 → fsync → rename”流程
路径和编码问题:Go 默认不处理 BOM,也不自动转路径分隔符
Go 的 os 包操作路径时,"a/b.txt" 在 Windows 上会被自动转为 a\b.txt,但如果你拼接字符串用了 "a\\b.txt" 再传给 os.Open,在 Linux 下就会打不开。BOM 同理:UTF-8 文件带 BOM 时,io.ReadAll 返回的字节开头是 0xEF 0xBB 0xBF,不手动剔除会导致 JSON 解析失败等静默错误。
立即学习“go语言免费学习笔记(深入)”;
- 路径拼接一律用
filepath.Join("a", "b", "c.txt"),不要用字符串+或fmt.Sprintf - 检测并跳过 UTF-8 BOM:
data, _ := os.ReadFile(path) if len(data) >= 3 && bytes.Equal(data[:3], []byte{0xEF, 0xBB, 0xBF}) { data = data[3:] } - 跨平台写文件时,换行符统一用
"\n"(Unix 风格),Windows 程序能正确识别;避免硬写"\r\n",除非明确要求兼容老旧工具










