bufio.reader 仅在频繁小块读取(如逐行、逐 token)时更快,因其减少系统调用;而 readall 或 io.copy 时套用反致多余拷贝。

bufio.Reader 什么时候比直接读文件快?
只有在频繁小块读取(比如逐行、逐 token)时,bufio.Reader 才有明显优势。它把系统调用 read() 的次数压下来了——底层一次读 4KB 到缓冲区,上层再慢慢切;而 os.File.Read() 每次都可能触发一次系统调用。
常见误用:对大文件一次性 ReadAll() 或 io.Copy() 时还套 bufio.Reader,反而多一次内存拷贝,纯属负优化。
- 适合场景:
Scanner.Text()、Reader.ReadString('\n')、Reader.ReadBytes('\n') - 缓冲区大小默认是 4096 字节,可通过
bufio.NewReaderSize(f, 64*1024)调大(如处理长日志行) - 注意:如果底层
io.Reader不支持Seek()(比如管道、网络连接),bufio.Reader的回退(UnreadByte()等)会受限
Scanner.Scan() 遇到超长行就 panic?
默认情况下,Scanner 单行上限是 64KB,超过会返回 scanner.ErrTooLong 错误,不是 panic,但容易被忽略——尤其处理用户上传或日志时。
解决办法是显式设置最大令牌长度:
立即学习“go语言免费学习笔记(深入)”;
sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 64*1024), 1<<20) // buf初始64KB,上限1MB
-
sc.Buffer()第二个参数是最大总容量,不能设为 0 或小于初始切片长度 - 设太大可能 OOM;设太小会频繁 realloc;建议按业务最长预期行长 × 1.5 估算
- 如果只是想跳过超长行,可捕获
sc.Err()判断是否为ErrTooLong后重置 scanner(需重新构造)
bufio.Writer 的 Flush() 忘调会导致数据丢失
bufio.Writer 是写缓冲,不 Flush() 就 close 或函数退出,缓冲区里没写出去的数据就丢了——这在日志、配置写入、网络响应等场景下很致命。
- 必须配对使用:
defer w.Flush()或在关键路径显式w.Flush()后检查错误 - 写完立即 flush 会影响性能,但某些场景(如实时日志)需要:
w.WriteString("msg"); w.Flush() - 如果底层 writer 是
net.Conn,未 flush 可能导致对方 recv 超时或收不到完整帧 - 别依赖
Writer.Close()自动 flush:它只对部分封装类型(如os.File)有效,对io.Writer接口不保证
混合使用 bufio.Scanner 和 bufio.Reader 容易出错
同一个底层 io.Reader 不能同时被 Scanner 和 Reader 消费——它们各自维护独立缓冲区,读取位置不同步,极易漏数据或重复读。
例如:先用 sc.Scan() 读几行,再用 r.ReadByte(),后者实际从 sc 缓冲区剩余部分开始,而非文件真实 offset。
- 方案一:全程只用
bufio.Reader(配合ReadString/ReadBytes) - 方案二:全程只用
Scanner,复杂解析逻辑用bytes.Split或正则处理sc.Text()返回的字符串 - 方案三:必须切换时,用
r.Discard(n)或手动同步读取位置(极少见,慎用)
缓冲 I/O 的核心不是“加一层就变快”,而是让缓冲策略匹配你的读写节奏。最常被忽略的,是忘记 flush 和跨缓冲区读取状态不一致。










