Go的csv.Reader默认不支持字段内换行,需设LazyQuotes=true、FieldsPerRecord=-1并确保字段用双引号包裹;中文乱码因Excel导出GBK且无BOM,须预转UTF-8;写CSV时数字布尔值需手动加引号防Excel误解析;大文件须流式Read避免OOM。

csv.Reader 读取含换行符的字段时直接 panic
Go 的 csv.Reader 默认不支持字段内换行(RFC 4180 允许用双引号包裹含换行的字段),一遇到就报 unexpected newline in record。这不是 bug,是默认严格模式——它只认纯逗号分隔、无嵌入换行的“扁平 CSV”。
实操上必须显式开启引号解析:
- 调用
reader.FieldsPerRecord = -1(允许每行字段数不固定,应对空字段或尾部缺失) - 设置
reader.LazyQuotes = true(容忍引号不配对,避免因数据脏而提前失败) - 最关键:确保输入数据本身用双引号包裹含换行字段,例如:
"name","desc\nline2","age"—— 如果源系统导出没加引号,csv.Reader无解,得先预处理
中文字段乱码(尤其是 Excel 导出的 CSV)
Go csv 包完全不处理编码,它只把字节流按 UTF-8 解释。但 Windows Excel 默认导出的是 GBK/GB2312,文件开头还没 BOM,csv.Reader 一读就成乱码。
不能靠“检测编码”糊弄,生产环境要确定源头:
立即学习“go语言免费学习笔记(深入)”;
- 若能控制导出端:强制 Excel 保存为 UTF-8(另存为 → 编码选 UTF-8)或加 BOM(用
utf8bom包写) - 若只能收外部文件:在
csv.NewReader前,用golang.org/x/text/encoding转码,例如从 GBK 到 UTF-8,再传给csv.NewReader - 别用
strings.ToValidUTF8这类简单替换,会吞掉真实非 UTF-8 字节,导致字段错位
写 CSV 时数字/布尔值被转成字符串但没加引号
csv.Writer 对 Write 传入的 []string 元素不做类型判断,所有内容原样写入。如果手动拼 fmt.Sprintf("%d", n) 或 strconv.FormatBool(b),结果就是裸数字,Excel 打开可能自动转科学计数法(如 1234567890123 → 1.23E+12)或丢前导零(如 "00123" → 123)。
- 安全做法:所有字段统一用
string类型,并对数字/布尔/日期等易变形字段主动加双引号,例如:fmt.Sprintf(`"%d"`, n) - 更稳方案:用
w.Write([]string{`"123"`, `"true"`, `"2024-01-01"`}),引号由你控制,不依赖 Excel 行为 - 注意:加引号后内部的双引号必须转义为两个双引号(
"a""b"),csv.Writer会自动做这事,但前提是字段本身是string且你没提前手动加错
大文件导入内存爆满(>100MB)
csv.Reader.ReadAll() 会把整个 CSV 加载进内存,字段多、行数多、单字段又大(比如 Base64 图片)时,GC 都来不及回收。不是慢,是直接 OOM。
- 必须用
for record, err := reader.Read(); err == nil; record, err = reader.Read()流式读取 - 每次
record是新分配的[]string,用完即丢,但注意别把它塞进长生命周期 map/slice 里攒着 - 如果要做校验或转换(如把字符串时间转
time.Time),在单次循环内做完,别留引用 - 额外提示:用
bufio.NewReader(fp).ReadSlice('\n')预切分再喂给csv.Reader并不省事,反而增加复杂度和错误点,标准库的Read()已足够高效
CSV 看似简单,但每个字段背后都可能是编码、引号、换行、空值、Excel 自作聪明的组合拳。别指望一个 ReadAll() 吃遍天下,关键在读之前就知道这文件到底是谁导的、用什么编码、有没有嵌套换行——这些信息比代码逻辑还重要。










