Go 语言的 I/O 编程避坑指南
Go 语言的 I/O 编程避坑指南
Go 写 I/O 很顺手,但也正因为顺手,很多坑会被写得很自然:一次性读大文件、忘记关闭文件、缓冲使用不当、错误被吞掉、路径拼接不跨平台。
这篇文章整理 Go 项目里最常见的 I/O 问题。
一、不要默认一次性读取大文件
小配置文件可以直接读:
1 | data, err := os.ReadFile("config.yaml") |
但日志、CSV、导出文件可能非常大,不应该随手 os.ReadFile。更稳的方式是流式读取:
1 | file, err := os.Open("access.log") |
这样内存不会被一个大文件直接打满。
二、Scanner 有默认长度限制
bufio.Scanner 很方便,但它默认单行最大 token 大约 64KB。读取超长日志行或大 JSON 行时,可能报 token too long。
可以手动调大缓冲:
1 | scanner := bufio.NewScanner(file) |
如果要处理真正的大块数据,bufio.Reader 往往比 Scanner 更合适。
三、写文件要注意缓冲和 flush
频繁小块写入时,推荐使用 bufio.Writer:
1 | file, err := os.Create("report.txt") |
Flush 不能省。否则数据可能还停在用户态缓冲区里,错误也不会及时暴露。
四、defer Close 也要关注错误
很多人会这样写:
1 | defer file.Close() |
读文件通常问题不大。但写文件时,关闭阶段也可能发生错误,比如网络文件系统异常或磁盘同步失败。
更严谨的写法是:
1 | if err := file.Close(); err != nil { |
如果已经用了 defer,至少在关键写入流程里确保 Flush、Sync 或最终关闭错误被处理。
五、路径不要手动拼接
不要这样写:
1 | path := dir + "/" + filename |
跨平台路径应该使用 filepath.Join:
1 | path := filepath.Join(dir, filename) |
Go 标准库里的 path 主要用于 URL 风格路径,文件系统路径应该优先用 path/filepath。
六、临时文件再替换更安全
直接覆盖配置或缓存文件,程序中途崩溃可能留下半个文件。更稳的方式是先写临时文件,再原子替换:
1 | tmp := path + ".tmp" |
关键状态文件、配置文件、索引文件,都值得用这种方式处理。
七、实践建议
Go 的 I/O 代码要特别关注三个问题:文件大小、错误处理、资源释放。
小文件可以简单写,大文件要流式处理。写文件要 Flush,打开文件要 Close,路径要用 filepath。这些都不是高级技巧,但能避免很多线上问题。