csv.NewReader 必须用 bufio.NewReaderSize(f, 64*1024) 包装 os.File,禁用 FieldsPerRecord 校验,写入时需 Flush,且须预处理 BOM 和非 UTF-8 编码。

csv.NewReader 传 os.File 会卡死,必须套 bufio.Reader
直接把 *os.File 传给 csv.NewReader 在机械盘或 NFS 上大概率卡顿甚至解析失败,不是代码写错了,是默认 4KB 缓冲太小。GB 级 CSV 每次只读 4KB,syscall 频繁触发,CPU 花在系统调用上,不是在解析数据。
- 必须显式包装:
csv.NewReader(bufio.NewReaderSize(f, 64*1024)),64KB 是实测甜点值——再大拖慢 GC,再小引号跨 buffer(比如字段含换行符且被双引号包裹)导致解析断裂 - 别用
bufio.Scanner替代:它按行切分,不理解 CSV 引号规则,遇到"a,b\n c"直接断成两行,字段错位 - 确认输入源可完整读取:最后一行若无换行符,
Read()可能返回io.EOF却不吐出该行;正确做法是err == nil时先处理record,再检查err
FieldsPerRecord = -1 不是可选,是必须
默认 reader.FieldsPerRecord > 0 会强制校验每行字段数一致,一不匹配就报 record on line X: wrong number of fields 并中断整个流。真实业务 CSV(用户上传、ERP 导出)几乎必然存在空行、行末逗号、引号内换行,根本过不了这关。
- 设
reader.FieldsPerRecord = -1关闭字段数校验,后续靠你自己逻辑判断有效性(比如检查关键字段是否为空) - 空行返回长度为 0 的
[]string,不是error,别当成异常panic或提前退出 - 若某列含换行符,
csv.Reader能正确解析,但前提是整行没被提前截断——这又回到缓冲区大小问题,64KB 是安全底线
写入不 Flush 就丢数据,裸写磁盘极慢
直接把 *os.File 传给 csv.NewWriter,每次 Write() 都触发一次 write syscall;GB 文件可能多花几倍时间。更糟的是,不手动 Flush(),最后几 KB 永远不落盘——程序 panic 或 kill 就丢数据。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 必须在写完后调用
w.Flush(),且建议在循环中定期Flush()(比如每 1000 行),避免内存积压 -
bufio.Writer+Flush()是标配,不加等于裸写磁盘;别以为defer w.Close()能兜底——Close()会Flush,但 panic 时 defer 不执行 - 不要先收集所有记录再
WriteAll:和ReadAll()是同一类陷阱,内存爆炸,GC 压力陡增
中文乱码、BOM、非 UTF-8 不是边缘情况,是默认行为
encoding/csv 完全不处理字符编码转换,只按字节流解析,假设输入是 UTF-8。Windows 记事本生成的 CSV 常带 UTF-8 BOM(\uFEFF),某些 ERP 导出的是 GBK——这些都会让 csv.Reader 把 BOM 当成第一列内容,或把 GBK 字节当非法 UTF-8 报错。
立即学习“go语言免费学习笔记(深入)”;
- 读取前先剥离 BOM:
bytes.TrimPrefix(data, []byte("\xef\xbb\xbf")),或对*os.File用io.MultiReader前置过滤 - GBK/GB2312 等编码需用
golang.org/x/text/encoding显式转 UTF-8,不能依赖标准库 - 别指望
os.Open自动识别编码——它只返回字节流,编码解析是上层责任

















