背景
如果 io.Reader 解决的是“统一输入抽象”的问题,那 bufio.Reader 解决的就是“读取效率和读取便利性”的问题。
它的核心价值主要有两类:
- 减少底层小块读取带来的系统调用开销
- 提供更方便的按字节、按行、按分隔符读取能力
核心概念
最常见的创建方式是:
1reader := bufio.NewReader(r)
这里的 r 可以是任何实现了 io.Reader 的对象。
它为什么更高效
bufio.Reader 内部会维护一个缓冲区。调用方如果频繁做小块读取:
- 底层并不是每次都重新向文件或网络要数据
- 很多读取动作会直接命中内存里的缓冲区
这在逐字节解析协议、逐行处理文本时尤其重要。
常用方法
ReadString / ReadBytes
适合按分隔符读数据,比如按换行读取:
1line, err := reader.ReadString('\n')
ReadByte
适合实现自定义解析器或状态机:
1b, err := reader.ReadByte()
Peek
适合“先看一眼、暂不消费”的场景:
1head, err := reader.Peek(4)
这在解析协议头、判断文件前缀时很好用。
UnreadByte
适合你读多了一字节,想退回去重新解析:
1b, _ := reader.ReadByte()
2_ = reader.UnreadByte()
但它不是任意回退工具,通常只支持退回最近一次成功读取的那个字节。
示例代码
下面是一个按行读取文本的例子:
1package main
2
3import (
4 "bufio"
5 "fmt"
6 "io"
7 "strings"
8)
9
10func main() {
11 src := "first line\nsecond line\n"
12 reader := bufio.NewReader(strings.NewReader(src))
13
14 for {
15 line, err := reader.ReadString('\n')
16 if len(line) > 0 {
17 fmt.Printf("line=%q\n", line)
18 }
19 if err == io.EOF {
20 break
21 }
22 if err != nil {
23 panic(err)
24 }
25 }
26}
使用时的边界
不是所有读取都该包 bufio.Reader
如果你本来就是一次性整块读取,或者底层已经有缓冲,再额外包一层未必有明显收益。
不要和底层 Reader 交替读
如果你已经创建了 bufio.Reader,就尽量通过它来读,不要又去直接读底层 r。否则缓冲区里的状态和底层位置会失去同步。
Peek 看到的是缓冲里的视图
拿到 Peek 的结果后,不要假设它能长期持有。后续继续读取可能会让这块底层缓冲被覆盖。
我的总结
bufio.Reader的本质是“带缓冲的 Reader 包装器”- 它既优化读取效率,也提供了更友好的逐段读取接口
ReadString、Peek、ReadByte都很常用,但要清楚各自语义- 创建了
bufio.Reader后,尽量不要和底层 Reader 混着读 - 它非常适合文本解析、协议解析和逐行消费场景