csv.NewReader必须传入io.Reader接口类型,如*os.File;不可直接传字符串路径。需先os.Open再defer Close,支持HTTP响应等各类io.Reader。ReadAll适合小文件,Read循环适合大文件和细粒度错误处理。

csv.NewReader 读取文件时必须传入 *os.File 或 io.Reader
直接传入文件路径字符串会编译失败,csv.NewReader 只接受实现了 io.Reader 接口的类型。常见错误是写成 csv.NewReader("data.csv"),这会报错 cannot use "data.csv" (type string) as type io.Reader。
- 正确做法:先用
os.Open打开文件,得到*os.File(它实现了io.Reader) - 别忘了用
defer f.Close(),否则文件句柄泄漏 - 如果数据来自 HTTP 响应体、bytes.Buffer 或 strings.Reader,同样可直接传入 —— 它们都满足
io.Reader
ReadAll 和逐行 Read 的选择取决于内存与错误处理需求
ReadAll 会一次性把整个 CSV 加载进内存并返回 [][]string,适合小文件且不关心某一行出错时继续解析的场景;而用循环调用 Read 可以逐行处理、及时响应错误、控制内存占用。
-
ReadAll遇到任何解析错误(如引号不匹配、字段数不一致)就直接返回错误,不提供出错行号 - 用
Read循环时,每行返回[]string,错误只影响当前行,可通过csv.ParseError类型断言获取Line和Field位置 - 大文件(>100MB)务必避免
ReadAll,否则可能 OOM
设置 Comma 和 TrimLeadingSpace 处理常见格式变体
默认分隔符是英文逗号 ,,但实际 CSV 可能用制表符、分号甚至中文顿号;另外 Excel 导出的 CSV 常在字段前带空格,不处理会导致字段值开头多出空格。
- 修改分隔符:
reader.Comma = '\t'(制表符)或reader.Comma = ';' - 自动裁剪字段首尾空格:
reader.TrimLeadingSpace = true(注意:只裁前导空格,不裁结尾) - 若需裁剪结尾空格,得手动对每行字段调用
strings.TrimSpace - 注意:这些设置必须在第一次调用
Read或ReadAll之前完成
package main
import (
"encoding/csv"
"fmt"
"os"
"strings"
)
func main() {
f, err := os.Open("users.csv")
if err != nil {
panic(err)
}
defer f.Close()
reader := csv.NewReader(f)
reader.Comma = ',' // 可省略,默认就是 ,
reader.TrimLeadingSpace = true
records, err := reader.ReadAll()
if err != nil {
if e, ok := err.(*csv.ParseError); ok {
fmt.Printf("parse error at line %d, field %d: %v\n", e.Line, e.Field, e.Err)
} else {
panic(err)
}
return
}
for i, record := range records {
if i == 0 {
continue // skip header
}
name := strings.TrimSpace(record[0])
email := strings.TrimSpace(record[1])
fmt.Printf("User %d: %s <%s>\n", i, name, email)
}
}
CSV 解析真正麻烦的不是读取本身,而是字段里嵌了换行符、双引号、逗号却没被正确转义 —— 这类问题不会在 Open 或 NewReader 阶段暴露,只会在 Read 时触发 csv.ParseError,而且错误信息里的 Line 是逻辑行号(含跨行字段),不是文件字节偏移,调试时容易误判。










