最稳妥方式是用 encoding/csv 的 csv.Writer 配合 *os.File,需确保文件以 os.O_CREATE|os.O_WRONLY|os.O_TRUNC 模式打开、每行调用 wr.Write([]string{...})、写完必须 wr.Flush() 和 file.Close(),并按需在文件开头写入 UTF-8 BOM(\xEF\xBB\xBF)以兼容 Windows 记事本。

用 encoding/csv 写入 CSV 文件最稳妥
Go 标准库的 encoding/csv 是写入 CSV 的首选,它自动处理字段中的逗号、换行符和双引号转义,避免手动拼接出错。直接用 csv.Writer 配合 *os.File 即可,不需要第三方包。
常见错误是忘记调用 wr.Flush() 或 file.Close(),导致最后一行数据没写入磁盘;还有人误用 fmt.Fprintln 直接写字符串,结果字段含换行时 CSV 结构被破坏。
- 写入前确保文件以
os.O_CREATE | os.O_WRONLY | os.O_TRUNC模式打开 - 每行用
wr.Write([]string{...}),不是WriteString - 写完必须调用
wr.Flush(),否则缓冲区内容可能丢失 - 结构体转 CSV 需手动提取字段,标准库不支持 tag 自动映射(如
csv:"name")
写入含中文或特殊字符的 CSV 要注意 BOM
Windows 记事本默认用 GBK 或 UTF-8 with BOM 识别中文,纯 UTF-8(无 BOM)可能显示乱码。Go 默认写的是无 BOM 的 UTF-8,所以 Excel 打开中文字段常变问号。
解决方法是在文件开头写入 UTF-8 BOM(\uFEFF),但不能直接写进 csv.Writer——得先写 BOM 到底层 *os.File,再把 csv.Writer 绑定到该文件。
立即学习“go语言免费学习笔记(深入)”;
- 打开文件后立即调用
file.Write([]byte("\xEF\xBB\xBF")) - 再用
csv.NewWriter(file),后续写入保持正常 - 不要对 BOM 做任何编码转换,它是字节序列,不是字符串
- 如果目标是跨平台程序(比如导出给 Mac/Linux 用户),BOM 反而可能引发解析异常,需按使用方要求决定是否添加
csv.Write 报 write error: bad file descriptor 怎么办
这个错误通常不是权限问题,而是文件对象已被关闭,或者 Writer 绑定在已关闭的文件上。典型场景是:函数内打开文件 → 创建 Writer → defer 关闭文件 → 然后调用 Write → 最后 defer 触发关闭 → 下次 Write 就崩了。
- 确保
file.Close()在所有wr.Write()和wr.Flush()之后执行 - 不要在循环中反复创建
csv.Writer,它本身带缓冲,复用更高效 - 如果用
defer file.Close(),把它放在wr.Flush()之后,或改用显式 close - 检查是否对同一文件句柄并发写入(比如多个 goroutine 共用一个
*csv.Writer),这会触发竞态且大概率报错
性能敏感场景下如何批量写入 CSV
单次 wr.Write() 写一行没问题,但循环写几千行时,频繁系统调用会影响速度。标准库的 csv.Writer 已内置缓冲(默认 4KB),但可以主动加大提升吞吐。
- 用
bufio.NewWriterSize(file, 64*1024)包一层,再传给csv.NewWriter - 避免在循环里做字段拼接或类型转换,提前准备好
[]string - 如果数据来自数据库查询,别用
rows.Scan()逐字段读,改用rows.Columns()+rows.Next()+rows.Values()一次性取整行[]interface{},再统一转 string - 内存充足时,可先写入
bytes.Buffer,最后一次性WriteTo到文件,减少 I/O 次数
CSV 写入真正的复杂点不在语法,而在边界场景:BOM 兼容性、大文件 flush 时机、多语言字段的编码一致性。这些地方一漏,下游用 Excel 或 Python pandas 打开就立刻暴露。










