bufio.Scanner适合按行读文本但需调大缓冲区,否则超长行报错;scanner.Text()返回字符串复用缓冲区,长期保存需显式拷贝;纯流式处理可直接用;bufio.Reader适合二进制或协议解析,支持Peek/Discard等精确控制能力。
直接用 os.file.read 和 os.file.write 处理大文件,性能差不是因为 go 慢,而是每次调用都触发一次系统调用——读 100 行小日志,可能就发起上百次内核切换。用 bufio 不是“加一层”,而是把零散请求攒起来批量处理,这是最简单、见效最快的优化手段。
bufio.Scanner 适合按行读文本,但缓冲区要手动调大
默认的 bufio.Scanner 缓冲区只有 64KB,遇到带 base64 或压缩 JSON 的日志行,scanner.Scan() 直接返回 scanner: token too long 错误——这不是文件太大,是单行超长撑爆了缓冲区。
- 用
scanner.Buffer()主动设上限:比如支持最长 1MB 的行,scanner.Buffer(make([]byte, 64*1024), 1024*1024) - 别设成
math.MaxInt32:万一输入缺换行符,会尝试分配数 GB 内存,直接 OOM -
scanner.Text()返回的字符串底层复用缓冲区,下一次Scan()就覆盖;若需长期保存,必须显式拷贝,比如append([]byte{}, scanner.Bytes()...)或string(scanner.Bytes()) - 纯流式处理(如统计、过滤)可直接用,不存引用就安全
bufio.Reader 更适合二进制或协议解析,避免 ScanLines 隐式行为
当你要精确控制读取边界(比如 HTTP header/body 分界、自定义协议头)、或需要 Peek() / Discard() 等能力时,bufio.Reader 是唯一选择——Scanner 封装太深,连错误类型都藏起来了。
- 初始化时指定缓冲区大小:
reader := bufio.NewReaderSize(file, 1(1MB),比默认 4KB 更适配大文件顺序读 - 读行用
reader.ReadString('\n')或更可控的reader.ReadBytes('\n')(后者返回[]byte,不触发 UTF-8 解码) - 注意
ReadString在超长行时仍会内存暴涨,此时应改用Read()+ 固定切片循环读,自己扫描换行符 -
Scanner.Err()才是真实错误源,Scan()返回true不代表成功——这点常被忽略,导致静默失败
bufio.Writer 写文件卡住或丢数据?大概率忘了 Flush
bufio.NewWriter 的缓冲区只在三种情况下真正落盘:缓冲区满、显式调用 Flush()、或 writer 被关闭。忘记 Flush() 是“写完了但文件空”“程序退出后内容消失”的头号原因。
- 写完必须调
w.Flush(),不能只靠defer w.Close()——Close()会调Flush(),但若Flush()失败(比如磁盘满),Close()也会失败,而你可能没检查返回值 - 更稳妥的收尾方式:
defer func() { w.Flush(); f.Close() }() - 缓冲区大小建议 32KB–256KB:太小(如 4KB)合并效果弱;太大(如 >1MB)GC 压力上升,且延迟落盘风险增加
- 网络写(如
net.Conn)更要检查Flush()返回的err:失败意味着连接已断,再写会 panic
io.Copy 是文件复制的最优解,别手写循环
用 io.Copy(dst, src) 复制文件,比任何手动 Read/Write 循环都快——它内部自动选择最佳路径:Linux 下优先用 sendfile 零拷贝,fallback 到带优化的用户态缓冲传输。
- 完全不用管缓冲区大小、分块逻辑、EOF 处理,代码简洁且无 bug 风险
- 若需进度回调或限速,用
io.CopyN或包装一个带计数的io.Reader - 注意
io.Copy不会关闭任一端,资源释放仍需手动Close() - 对 SSD 有效,对机械硬盘也稳——它不依赖并发,只优化单流吞吐
缓冲区不是越大越好,goroutine 不是越多越快,Flush() 不是可有可无。真正的瓶颈往往藏在“默认值”和“忘了检查错误”里——尤其 Scanner.Err() 和 Writer.Flush() 这两个点,90% 的线上 IO 问题都出在这儿。



















