如何高效读取大型纯文本文件(62MB+)并逐行处理

8次阅读

如何高效读取大型纯文本文件(62MB+)并逐行处理

本文介绍在 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,无需额外适配。

✅ 总结:三步走策略

  1. 首选 bufio.Scanner:语义清晰、内置换行处理、错误明确;
  2. 务必调用 sc.Buffer() 设置合理 maxSize(根据业务最长行预估,如 1–5MB);
  3. 始终检查 sc.Err(),区分 io.EOF、bufio.ErrTooLong 与其他 I/O 错误。

只要避开缓冲区陷阱,Go 完全能轻松驾驭百 MB 级文本文件 —— 关键不在“能不能”,而在“怎么配”。

text=ZqhQzanResources