
本文介绍在 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(\r\n)、Unix(\n)、Mac(\r)混用可能导致解析异常。bufio.Scanner 自动处理 \r\n 和 \n,无需额外适配。
✅ 总结:三步走策略
- 首选 bufio.Scanner:语义清晰、内置换行处理、错误明确;
- 务必调用 sc.Buffer() 设置合理 maxSize(根据业务最长行预估,如 1–5MB);
- 始终检查 sc.Err(),区分 io.EOF、bufio.ErrTooLong 与其他 I/O 错误。
只要避开缓冲区陷阱,Go 完全能轻松驾驭百 MB 级文本文件 —— 关键不在“能不能”,而在“怎么配”。










