
本文介绍在 go 中安全、高效读取大文本文件(如 62mb、33 万行)的正确方法,重点解决 `bufio.scanner` 缓冲区溢出、`readline` 使用误区及超长行处理问题,并提供生产就绪的逐行解析与内存优化方案。
在 go 中处理大型纯文本文件(如您描述的 62.1 MB、339,276 行)时,核心挑战并非“文件太大”,而是单行过长导致默认缓冲区不足——bufio.Scanner 默认缓冲区仅 64 KiB(65,536 字节),一旦某行长度超过此值,Scan() 就会返回 scanner.ErrTooLong 错误(而非静默失败或 panic),而您的代码中未捕获该错误,导致看似“卡住”或“崩溃”。
✅ 正确做法:优先使用 bufio.Scanner,但必须自定义缓冲区
bufio.Scanner 是 Go 官方推荐的逐行读取方式,简洁、安全、可扩展。只需显式增大缓冲区即可应对超长行:
file, err := os.Open(feedFolder + value) if err != nil { handleError(err) } defer file.Close() // 创建 scanner 并设置足够大的缓冲区(例如 1MB) sc := bufio.NewScanner(file) sc.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // min=0, max=1MB var linesInFile []string for sc.Scan() { line := sc.Text() // 安全获取字符串(自动处理 UTF-8 和换行符) linesInFile = append(linesInFile, line) // ✅ 关键:检查扫描错误(尤其是 ErrTooLong) if err := sc.Err(); err != nil { if errors.Is(err, bufio.ErrTooLong) { log.Printf("警告:跳过超长行(>1MB),位置:%d", len(linesInFile)) continue // 或按需截断/报错 } handleError(err) return } } fmt.Printf("成功读取 %d 行n", len(linesInFile))
? 为什么 r.ReadLine(“n”) 不工作? 您调用的 r.ReadLine(“n”) 是无效语法 —— bufio.Reader.ReadLine() 不接受参数,其签名是 func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)。isPrefix 正是用来标识“当前行是否因缓冲区满而被截断”的关键标志。若 isPrefix == true,说明该行未读完,需循环调用 ReadLine() 直到 isPrefix == false,否则数据丢失。
⚠️ 若必须用 ReadLine:手动处理 isPrefix
r := bufio.NewReader(file) var linesInFile []string for { var line []byte var isPrefix bool var err error // 循环读取直到整行完整(处理超长行) for { var chunk []byte chunk, isPrefix, err = r.ReadLine() line = append(line, chunk...) if !isPrefix || err != nil { break } } if err != nil { if errors.Is(err, io.EOF) { break // 文件结束 } handleError(err) return } linesInFile = append(linesInFile, string(line)) }
? 内存与性能建议(针对 62MB+ 场景)
- 避免一次性加载全部内容到内存:os.ReadFile() 或 ioutil.ReadFile() 会将整个文件载入 RAM(62MB → 至少 62MB+ GC 开销),对后续数据库批量插入并无优势,反而增加 OOM 风险。
- 流式处理更优:边读边解析、边过滤、边批量入库(如每 1000 行 INSERT INTO … VALUES (…), (…))。示例:
const batchSize = 1000 var batch []string for sc.Scan() { line := sc.Text() if yourCondition(line) { batch = append(batch, line) if len(batch) >= batchSize { insertBatchToDB(batch) batch = batch[:0] // 复用切片 } } } if len(batch) > 0 { insertBatchToDB(batch) // 处理剩余 } - 确认换行符一致性:windows(rn)、unix(n)、mac(r)混用可能导致解析异常。bufio.Scanner 自动处理 rn 和 n,无需额外适配。
✅ 总结:三步走策略
- 首选 bufio.Scanner:语义清晰、内置换行处理、错误明确;
- 务必调用 sc.Buffer() 设置合理 maxSize(根据业务最长行预估,如 1–5MB);
- 始终检查 sc.Err(),区分 io.EOF、bufio.ErrTooLong 与其他 I/O 错误。
只要避开缓冲区陷阱,Go 完全能轻松驾驭百 MB 级文本文件 —— 关键不在“能不能”,而在“怎么配”。