bufio.writer.write不等于写入文件,数据仅存于内存缓冲区,必须显式调用flush()才能落盘;否则程序panic、os.exit或主goroutine结束时数据必然丢失。

bufio.Writer 不是“写函数”,它是个带缓冲的 io.Writer 实现——你用它写,但必须自己管“交出去”的时机。
不调用 Flush() 就等于没写
这是最常踩的坑:w.Write([]byte("log")) 只把数据拷进内存缓冲区,不触发系统调用;os.Exit(0)、panic 或 main goroutine 结束时,缓冲区里剩的数据直接丢弃。
- 日志场景下,最后一条错误日志消失,不是“可能没落盘”,而是“几乎肯定没落盘”
-
w.Close()仅对*os.File底层才保证 flush;若写到os.Stdout或net.Conn,Close()很可能只关连接,不刷缓冲 - 稳妥写法:
defer func() { _ = w.Flush() }(),再按需w.Close()
WriteString 和 Write 的行为差异
二者都只是填充缓冲区,不落盘。区别在于参数类型和底层处理:
-
w.WriteString("hello"):内部转成[]byte后调w.Write,开销略小 -
w.Write([]byte{...}):直接操作字节切片,适合已知字节序列的场景(如二进制协议头) - 两者都不检查换行或编码;UTF-8 多字节 rune 写入完全没问题,
WriteRune也一样走缓冲
缓冲区大小设多少才合理
默认 4096 字节(即 4KB)对多数文本写入够用,但不是万能解:
- 批量导出 CSV/JSON:用
bufio.NewWriterSize(f, 64*1024),攒够 64KB 再 flush,吞吐更稳 - 实时日志(每行一条):保持默认大小,但每条后加
w.Flush(),确保可追溯 - 不确定节奏时,别硬编码大缓冲(比如 1MB),优先靠
Flush()控制时机,而非靠 size “赌”行为
别和 io.Copy 混着用
看起来很自然:io.Copy(w, r),其中 w 是 bufio.NewWriter(f)。但实际风险高:
-
io.Copy只调w.Write,从不Flush;结尾数据卡在缓冲区是常态 - 某些组合(如
r是管道或非阻塞 reader)下,bufio.Writer内部状态机可能误判 EOF,导致卡死或提前终止 - 安全做法:源和目标都用原始
io.Reader/io.Writer;需要缓冲时,单独 wrap 并自己控制Flush()
真正难的不是“怎么开缓冲”,而是“什么时候交出去”。很多人盯着 NewWriterSize 参数调来调去,却忘了 flush 才是数据落地的最后一道门——它不自动,也不智能,全靠你显式触发。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











