Go 1.16前推荐用ioutil.ReadFile/WriteFile处理小文件,之后应改用os.ReadFile/WriteFile;须检查error,注意UTF-8解码、内存占用及覆盖写入限制;大文件宜用bufio.Scanner按行读取,并注意调整Buffer避免“token too long”错误。

用 ioutil.ReadFile 和 ioutil.WriteFile 快速读写小文件
Go 1.16 之前推荐用 ioutil.ReadFile 和 ioutil.WriteFile,它们封装了打开、读取/写入、关闭全过程,适合处理几 MB 以内的文本文件。注意:Go 1.16 起 ioutil 已被弃用,应改用 os.ReadFile 和 os.WriteFile(行为完全一致,只是包路径变了)。
常见错误是忽略返回的 error,导致文件不存在或权限不足时静默失败。实际使用必须检查错误:
data, err := os.ReadFile("config.txt")
if err != nil {
log.Fatal("读取失败:", err) // 不要只打印 err,至少带上下文
}
text := string(data) // 注意:默认按 UTF-8 解码,非 UTF-8 编码会乱码
- 仅适用于小文件;大文件(>10MB)容易触发内存暴涨
- 不支持追加写入;
os.WriteFile总是覆盖整个文件 - 没有中间缓冲控制,无法处理流式场景(如边读边解析)
用 bufio.Scanner 按行读取大文本文件
当文件超过几十 MB,或需要逐行处理(如日志分析、CSV 行解析),直接读全量进内存不可取。bufio.Scanner 是标准库提供的高效行读取器,内部自带缓冲,默认 64KB,可避免频繁系统调用。
关键点在于:它默认限制单行最大长度为 65536 字节,超长行会报 "scanner: token too long" 错误——这是最常踩的坑。
立即学习“go语言免费学习笔记(深入)”;
f, _ := os.Open("access.log")
defer f.Close()
scanner := bufio.NewScanner(f)
// 若需支持超长行,必须显式设置缓冲区大小
scanner.Buffer(make([]byte, 64*1024), 1<<20) // max 1MB 行长
for scanner.Scan() {
line := scanner.Text() // 不含换行符
processLine(line)
}
if err := scanner.Err(); err != nil {
log.Fatal("扫描出错:", err)
}
-
scanner.Text()返回的是字符串副本,修改它不影响底层缓冲;如需原字节操作,用scanner.Bytes() - 不要在循环内反复调用
scanner.Scan()后再检查scanner.Err()—— 必须在循环结束后检查一次,否则 I/O 错误可能被遗漏 - 不适用于需要按字段(而非按行)切分的场景,比如固定宽度格式;此时应改用
bufio.Reader+ 手动ReadString或ReadBytes
用 os.OpenFile + bufio.Writer 追加写入与性能优化
追加写入不能用 os.WriteFile,必须用 os.OpenFile 配合 os.O_APPEND | os.O_WRONLY | os.O_CREATE 标志。但直接对返回的 *os.File 调用 Write 效率低,每次都是系统调用。加一层 bufio.Writer 能显著提升吞吐量。
f, err := os.OpenFile("output.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
w := bufio.NewWriter(f)
defer w.Flush() // 必须调用,否则最后一批数据可能丢失
for _, msg := range logs {
_, _ = fmt.Fprintln(w, msg) // 自动加 \n
}
-
bufio.NewWriterSize(f, 4096)可自定义缓冲大小;SSD 上 4–8KB 通常较优,HDD 可稍大 - 若写入过程中发生 panic,
defer w.Flush()仍会执行,但部分数据可能已落盘、部分还在缓冲中——无法保证原子性 - 并发写同一文件需额外加锁;
bufio.Writer本身不是 goroutine 安全的
编码问题:Go 默认只认 UTF-8,GBK/Shift-JIS 文件需转码
Go 的 string 类型语义上是 UTF-8 编码的字节序列,os.ReadFile 返回的 []byte 也未做任何编码转换。如果文件是 GBK 编码(常见于 Windows 旧日志),直接 string(data) 会导致中文乱码,且 range 遍历 rune 也会出错。
解决方法是用第三方库(如 golang.org/x/text/encoding)显式解码:
data, _ := os.ReadFile("old.log")
decoder := simplifiedchinese.GBK.NewDecoder()
text, _ := decoder.String(string(data))
- 不要尝试用
bytes.ReplaceAll或正则“修复”乱码字节——编码错误无法靠替换恢复 - 写入非 UTF-8 文件时,同样需用对应编码器 encode 后再写
[]byte,不能直接 write string - 没有运行时自动探测编码的可靠方案;必须明确知道源文件编码,或由用户指定
真正麻烦的从来不是“怎么读”,而是“读出来是不是你想要的字”。编码、换行符(\r\n vs \n)、BOM 头、空行处理、超长行截断——这些细节不显眼,却决定脚本能不能在真实环境里跑通。










