Golang文件读写的基本流程与注意事项

17次阅读

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

Golang文件读写的基本流程与注意事项

open 和 close 必须成对出现,否则文件句柄泄漏

Go 中用 os.Openos.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,可调 ScanBytesBufio.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 上会被自动转为 ab.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 程序能正确识别;避免硬写 "rn",除非明确要求兼容老旧工具

文件读写看着简单,但权限、原子性、编码、路径、并发这五处,任一疏忽都会在上线后某天凌晨三点弹出告警。

text=ZqhQzanResources