绝大多数小文件读写直接用 os.ReadFile 和 os.WriteFile 即可,它们自动处理打开、读写、关闭;但需检查 error、避免内存溢出、注意覆盖行为及字节切片转换;大文件须用 os.Open + io.Copy 并 defer 关闭;路径用 filepath.Join,换行符需跨平台适配。

用 os.ReadFile 和 os.WriteFile 快速读写小文件
绝大多数场景下,直接用 os.ReadFile 和 os.WriteFile 就够了——它们封装了打开、读/写、关闭全过程,不易出错,代码也干净。
常见错误是忽略返回的 error,比如文件权限不足或路径不存在时,os.ReadFile 会返回 "permission denied" 或 "no such file or directory",不检查就 panic。
-
os.ReadFile默认读取全部内容到内存,不适合几百 MB 以上的文件 -
os.WriteFile会覆盖原文件;若要追加,得换os.OpenFile+os.O_APPEND - 第二个参数是
[]byte,别传string而不转:用[]byte("hello")或[]byte(s),不是string([]byte)
示例:
data, err := os.ReadFile("config.json")
if err != nil {
log.Fatal(err) // 别只打印,至少处理
}
// ...
err = os.WriteFile("output.txt", []byte("done"), 0644)
if err != nil {
log.Fatal(err)
}
大文件或流式处理必须用 os.Open + io.Copy
读 GB 级日志、传输文件、边读边处理(如解密、压缩),就不能把整个文件 load 进内存。这时得手动控制句柄生命周期,配合 io.Copy 或 bufio.Scanner。
容易踩的坑是忘记 Close(),导致文件句柄泄漏,Linux 下最多打开 1024 个文件,跑久了会卡死在 "too many open files"。
- 用
defer f.Close()是最稳妥的,但注意它在函数 return 后才执行,如果中间 panic 且没 recover,仍可能漏关 -
io.Copy(dst, src)内部按 32KB 分块复制,不用自己写循环,性能和内存都友好 - 如果需要逐行读(比如解析 CSV),优先用
bufio.NewScanner(f),比bufio.NewReader+ReadString(' ')更安全(自动处理长行截断)
示例(复制大文件):
src, err := os.Open("large.zip")
if err != nil {
log.Fatal(err)
}
defer src.Close()
dst, err := os.Create("large_copy.zip")
if err != nil {
log.Fatal(err)
}
defer dst.Close()
_, err = io.Copy(dst, src) // 不关心 copied bytes 数,只看 err
if err != nil {
log.Fatal(err)
}
os.OpenFile 的 flag 组合决定行为边界
os.OpenFile 是底层统一入口,os.ReadFile、os.Create 都是它的封装。真正控制“能不能写”“找不到是否创建”“是否清空”“是否追加”的,就是那几个 flag 常量。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
最常混淆的是 os.O_CREATE | os.O_WRONLY 和 os.O_CREATE | os.O_RDWR:前者只能写不能读,后者可读可写,但默认从开头写,会覆盖已有内容。
- 追加写:必须带
os.O_APPEND,且通常搭配os.O_CREATE和os.O_WRONLY - 只读打开已存在文件:
os.O_RDONLY,文件不存在就报错,不会创建 -
os.O_TRUNC很危险:只要文件存在,就会清空内容,哪怕你只想追加 - 权限掩码(第三个参数)只在创建新文件时生效,对已存在文件无效
示例(安全追加日志):
f, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = f.WriteString(time.Now().Format("2006-01-02 15:04:05") + " info: started
")
if err != nil {
log.Fatal(err)
}
Windows 路径分隔符和换行符问题不能靠猜测
Go 的 os 包内部已做平台适配,os.ReadFile 读出来的 []byte 是原始字节,Windows 下换行是
,Linux/macOS 是
。如果你在 Windows 上生成 JSON 写入 Linux 容器,又没做标准化,某些 parser 可能报错。
路径拼接别手写 "dir\sub\" + name 或 "dir/sub/" + name,一律用 path/filepath.Join("dir", "sub", name),它会根据 OS 返回正确分隔符。
-
filepath.Join会自动清理重复分隔符和"..",但不会检查路径是否存在或是否越界(比如../../../etc/passwd) - 跨平台写文本时,如果要求严格 LF 换行,写入前用
bytes.ReplaceAll(data, []byte(" "), []byte(" "))统一 - 读配置文件时,别假设用户一定用 Unix 风格换行,
strings.TrimSpace比strings.TrimRight(data, " ")更鲁棒
真正麻烦的是符号链接和挂载点:Go 的 os.Stat 返回的是目标文件信息,os.Lstat 才返回链接本身。如果业务逻辑依赖路径是否为 symlink,必须显式调用 Lstat。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










