bufio.newreader 在频繁读小块数据时更快,因其用内存缓冲区合并系统调用;适用日志、csv等流式读取,不适用大文件直读;缓冲区大小可调;scanner 默认64kb行限会panic,需显式设置。

bufio.NewReader 为什么比 os.File.Read 快
因为 os.File.Read 每次调用都触发一次系统调用,而 bufio.NewReader 在内存里维护一块缓冲区(默认 4KB),把多次小读取合并成一次系统调用。不是“一定快”,而是“在频繁读小块数据时显著快”。
- 适用场景:逐行读日志、解析 CSV、处理 HTTP 响应体等流式输入
- 不适用场景:读一个 2MB 的图片文件——此时直接
io.ReadFull或os.ReadFile更合适,避免多一层内存拷贝 - 缓冲区大小可调:
bufio.NewReaderSize(reader, 64*1024),大文件流 + 高吞吐时调到 64KB 以上能再降 10%~20% 系统调用开销 - 注意:
bufio.Reader的Read方法可能返回io.EOF,但内部缓冲区里还有未消费的数据;用Peek或UnreadRune时要小心边界
bufio.Scanner 扫描超长行直接 panic
bufio.Scanner 默认最大行长度是 64KB,超过就报 scanner: token too long 错误,而不是返回 ErrTooLong——它直接 panic,且无法 recover,服务容易挂。
- 必须显式设置上限:
scanner := bufio.NewScanner(r); scanner.Buffer(make([]byte, 4096), 1(第二参数是最大 token 长度,这里设为 1MB) - 如果不确定行长,别用
Scan,改用Reader.ReadString('\n')+ 手动拼接,自己控制内存和错误处理 -
Scanner是为“格式干净、行不太长”的文本设计的,不是通用流处理器;解析用户上传的任意文本时尤其危险
bufio.Writer.Write 不保证写入磁盘,flush 漏掉就丢数据
bufio.Writer 把数据先写进内存缓冲区,调用 Write 只是 memcpy,真正落盘靠 Flush。进程退出前没 Flush,或者 defer w.Flush() 写在错误分支之后,数据就静默丢失。
- 常见错误:在
for循环里反复w.Write,最后只Close,但Close不等于Flush(除非是bufio.NewWriterSize(f, n)包裹的 *os.File,它的Close会 flush) - 安全写法:每次关键写入后
if err := w.Flush(); err != nil { ... },或用defer func() { _ = w.Flush() }()确保执行 - 性能权衡:太频繁
Flush失去缓冲意义;太懒则风险高。日志类场景建议每条记录后Flush,批量导出可攒够 1MB 再刷
bufio 包不能替代 io.Copy,混用可能卡死
把 bufio.Reader 和 bufio.Writer 直接传给 io.Copy 看似合理,但底层可能因缓冲区对齐、边界判断逻辑冲突,导致复制提前终止或阻塞——尤其当源是管道或网络连接时。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:
io.Copy(dst, src)就用原始io.Reader/io.Writer;需要缓冲时,用bufio.NewReader(src)或bufio.NewWriter(dst)单侧增强,别两边都包 - 如果必须双缓冲(比如边读边解密再写),手动循环:
for { n, err := reader.Read(buf); ... writer.Write(buf[:n]); ... },自己控制 flow -
bufio的缓冲是“偷懒优化”,不是 I/O 协议层抽象;跨 goroutine 共享同一个bufio.Reader也容易因Read和Peek竞态出错
缓冲区大小、panic 边界、flush 时机、接口混用——这四点不细看文档很容易踩进静默失败的坑。尤其是线上服务里用 Scanner 或忘掉 Flush,问题往往延迟暴露,查起来费时间。











