bufio.Writer是连续写入大量文本的唯一合理选择,因其通过缓冲合并小写入、大幅减少系统调用;默认4KB缓冲偏小,建议设为64KB–128KB;必须显式Flush()才能落盘,且需注意并发安全与缓冲区大小权衡。

直接用 bufio.Writer,别碰 os.WriteFile 或裸 os.File.WriteString —— 后两者在连续写入场景下会迅速成为性能瓶颈。
为什么 bufio.Writer 是唯一合理选择
连续写入大量文本(比如日志流、CSV导出、批量生成报告)本质是高频小块写操作。每次调用 os.File.WriteString 或 os.File.Write 都可能触发一次系统调用,而系统调用开销远高于内存拷贝。实测写入 10 万行文本时,裸写可能触发数万次 write() 系统调用;用 bufio.NewWriterSize(f, 64*1024) 后通常只需几十次。
- 默认
bufio.Writer缓冲区是 4KB,对连续文本写入偏小,建议显式设为64*1024或128*1024 - 缓冲区不是越大越好:超过 1MB 容易拖慢响应,且单次
Flush()延迟明显上升 -
os.WriteFile内部会把整个内容复制进内存再一次性写入,写 100MB 文本会分配等量内存,还无法追加
Flush() 忘记调用 = 数据没写进去
这是最常踩的坑。所有写入操作(WriteString、Write、WriteRune)都只进缓冲区,不落盘。程序退出前没 Flush(),数据就永远卡在内存里。
- 必须在
defer f.Close()之前调用wr.Flush(),否则Close()不保证刷盘 - 若需实时可见(如日志),可在关键位置手动
Flush();但频繁调用会抵消缓冲收益 - 错误处理不能只看
WriteString返回值:它几乎总返回nil错误,真正错误在Flush()时才暴露
并发写同一文件必须加锁,bufio.Writer 不是 goroutine 安全的
多个 goroutine 共享一个 bufio.Writer 实例会导致写乱序、panic 或静默数据损坏——因为内部缓冲区和偏移量没有同步保护。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:用
sync.Mutex包一层,或每个 goroutine 独占一个bufio.Writer(需复用底层*os.File) - 更推荐方案:用 channel 收集待写内容,单个 goroutine 持有
bufio.Writer负责串行刷盘,避免锁竞争 - 不要试图用
io.MultiWriter组合多个bufio.Writer:它只是把写操作广播出去,不解决并发安全问题
超大文本写入前可预分配文件空间
当已知最终大小(例如导出固定结构的百万行 CSV),用 f.Truncate(size) 提前分配磁盘空间,能显著减少文件系统碎片和扩展开销,尤其在机械硬盘或某些网络文件系统上效果明显。
- 预分配后仍需正常写入,
Truncate只影响文件长度,不改变写入逻辑 - 注意:
Truncate会清空原文件内容,若需追加,先Seek到末尾再写 - 该优化对 SSD 效果有限,但无害;对 HDD 或 NFS 可提升 10%~30% 写入吞吐
缓冲区大小、Flush() 时机、并发控制这三点,漏掉任一都会让 bufio.Writer 的优势归零。实际压测中,经常发现“用了缓冲却比裸写还慢”,根源几乎全是这三个地方没对齐。


















