bufio.reader 在频繁读小块数据时更快,因其用用户态缓冲合并系统调用;但小文件或单次大读取无收益,且不支持 seek、有行长度限制,需谨慎配置缓冲区。

bufio.Reader 为什么比 os.File.Read 快?
因为 os.File.Read 每次调用都触发一次系统调用,而 bufio.Reader 在用户态维护一块缓冲区,把多次小读取合并成一次大系统调用。不是“一定快”,而是“在频繁读小块数据时明显快”。
- 小文件或单次大读取(如
io.ReadAll)用bufio.Reader反而多一层拷贝,没收益 - 默认缓冲区大小是 4096 字节,对 HTTP header 或日志行这类短文本很合适;但读超长行(比如 >64KB 的 CSV 字段)可能触发
bufio.Scanner: token too long错误 - 如果底层
io.Reader本身不支持Seek(如网络连接),bufio.Reader也无法倒回——它只缓存已读未消费的部分,不能“未读先缓”
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n') // 这行内部可能从内核只读一次,却返回多个 \n 分隔的字符串bufio.Scanner 读文件时突然停在某一行?
这是最常见的误用:默认的 bufio.Scanner 有 64KB 的单行长度限制,超过就直接报错 scanner: token too long,且不会告诉你哪一行——它静默失败。
- 不要把它当通用行读取器用在不可信输入上(比如用户上传的文本、日志归档)
- 真需要读长行,必须显式设置缓冲区:
scanner.Buffer(make([]byte, 64*1024), 1(第二个参数是最大令牌长度) -
scanner.Scan()返回 false 时,得立刻检查scanner.Err(),否则会漏掉错误 - 如果要保留换行符、或按非
\n分隔(比如\r\n),别用Scan(),改用reader.ReadString('\n')或reader.ReadBytes('\n')
bufio.Writer 写入后内容没出现在文件里?
因为 bufio.Writer 的写操作只进缓冲区,不保证落盘。Write 调用成功 ≠ 数据已写入磁盘或文件。
- 必须调用
writer.Flush()才会把缓冲区内容真正写到底层io.Writer - 如果程序 panic 或提前 exit,没
Flush()的数据就丢了(尤其写日志、配置文件时很致命) -
defer writer.Flush()不可靠:如果defer后还有写操作,顺序不对;更稳妥的是在关键路径结尾显式调用 - 缓冲区大小影响性能但不改变语义:设太小(如 128 字节)导致频繁 flush;设太大(如 1MB)可能延迟可见性,还浪费内存
w := bufio.NewWriter(file)
w.WriteString("hello")
w.Flush() // 这行不能省什么时候不该用 bufio?
缓冲层不是银弹。以下情况绕过 bufio 更直接、更可控:
立即学习“go语言免费学习笔记(深入)”;
- 需要精确控制每次系统调用边界(比如实现自定义协议解析,靠 read 返回字节数做状态机)
- 底层是内存
[]byte或strings.Reader,本身无 I/O 开销,加bufio纯属冗余 - 做 benchmark 对比时,不加缓冲才能测出真实 syscall 开销
- 使用
encoding/json.Decoder或xml.Decoder时,它们内部已自带缓冲逻辑,再套一层bufio.Reader只是徒增间接层
缓冲区大小不是越大越好,也不是越小越安全;它是个权衡:内存占用、延迟、系统调用频次。线上服务中,观察实际读写模式比拍脑袋设 4KB 更有效。











