用 encoding/csv 读取标准 CSV 文件最稳妥,但需确保 UTF-8 无 BOM;GBK 等编码须先转码;大文件应流式 Read 避免 OOM;写入时需防 Excel 公式注入。

用 encoding/csv 读取标准 CSV 文件最稳妥
Go 标准库的 encoding/csv 包是处理合规 CSV 的首选,它能正确解析带引号、换行、逗号转义的字段,不用自己写状态机。但前提是文件编码为 UTF-8,且无 BOM;遇到 GBK 或含 BOM 的文件会直接报错 invalid UTF-8。
常见错误现象:record on line X: wrong number of fields —— 多半是某行字段被双引号包裹后内部含换行符,而你用了 Read() 而非 ReadAll(),导致按行切割失败。
- 始终用
csv.NewReader(file)包裹*os.File或io.Reader,别直接读字节流 - 如果首行为表头,调用一次
r.Read()获取 header,再用r.ReadAll()读余下数据(ReadAll内部已处理跨行字段) - 字段含双引号时,Go 默认要求成对出现且被引号包裹,如
"abc""def"表示abc"def;不支持单引号或反斜杠转义
file, _ := os.Open("data.csv")
r := csv.NewReader(file)
records, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
for i, record := range records {
fmt.Printf("row %d: %v\n", i, record)
}
处理带 BOM 或非 UTF-8 编码的 CSV 文件
Windows 记事本保存的 CSV 常带 UTF-8 BOM(\xEF\xBB\xBF),csv.NewReader 会把它当非法字符报错。GBK/GB2312 文件更常见于老系统导出数据,标准包完全不识别。
- 检测并跳过 UTF-8 BOM:读取前 3 字节,若为
\xEF\xBB\xBF,则用io.MultiReader(bytes.NewReader([]byte{}), file)跳过 - GBK 文件必须先转码:用
golang.org/x/text/encoding/charmap中的GB18030(兼容 GBK)解码,再传给csv.NewReader - 不要用
strings.ReplaceAll或正则“删 BOM”——BOM 可能出现在任意位置,且 UTF-8 多字节序列不能简单字节替换
file, _ := os.Open("gbk.csv")
defer file.Close()
decoder := charmap.GB18030.NewDecoder()
reader := transform.NewReader(file, decoder)
r := csv.NewReader(reader)
records, _ := r.ReadAll()
大文件场景下避免 ReadAll 导致 OOM
ReadAll 会把整个文件加载进内存,100MB CSV 可能吃掉 500MB+ 内存(字符串重复分配、slice 扩容)。真实业务中应流式处理,尤其做 ETL 或导入数据库时。
立即学习“go语言免费学习笔记(深入)”;
- 用
for record, err := r.Read(); err == nil; record, err = r.Read()循环逐行读,每行处理完立即释放引用 - 注意
record是切片,底层共用同一块缓冲区,若需长期保存某行,必须append([]string{}, record...)拷贝 - 设置
r.Comma = '\t'可读 TSV;r.TrimLeadingSpace = true自动去字段前导空格(但不去引号内空格)
写入 CSV 时防止 SQL 注入或 XSS 的关键点
CSV 本身不是执行环境,但若导出数据被 Excel 打开,字段以 =、+、-、@ 开头,Excel 会误判为公式执行——这属于客户端风险,但服务端可主动规避。
- 对每个字段做前置检查:若字符串以
=、+、-、@开头,统一在前面加单引号'(Excel 会显示该引号,但不执行) - 不要依赖
csv.Write自动加引号:它只在含逗号、换行或引号时才包裹,对公式前缀无效 - 若字段含 HTML 标签(如从富文本提取),不建议在 CSV 中保留——CSV 不是展示格式,应导出纯文本或另附 HTML 版本
真正难处理的是嵌套结构(如 JSON 字段)、超长字段截断、以及缺失值统一标记(NULL 还是空字符串)。这些得结合业务定规则,标准库只管语法,不管语义。










