Go Scanner 与 Reader 处理大文件的选择依据
来源:17golang原创
时间:2026-10-28 03:26:11 363浏览 收藏
处理大文件时,文件总大小通常不是 Go bufio.Scanner 和 bufio.Reader 的决定因素,单条记录的长度、分隔方式以及你要不要控制读取错误才是关键。短行、规则文本优先用 Scanner;可能出现超长行、需要保留分隔符或必须逐块恢复时,用 Reader 更稳妥。
官方文档:https://pkg.go.dev/bufio
Scanner适合把输入切成 token,默认按行切分,默认 token 缓冲上限是 64 KiB。- 能估计最大记录长度时,在第一次
Scan前用Buffer调整上限。 Reader适合超长记录、保留分隔符、读取片段和更细的 EOF/错误处理。
先看记录边界,再决定 Scanner 还是 Reader
“大文件”只代表输入总量大,不代表必须把整个文件放进内存。两种类型都可以从 io.Reader 流式读取,差别在于每次交给业务的单位:Scanner 交付一个 token,Reader 让调用方自己决定按字节、按分隔符还是按片段消费。
| 场景 | 优先选择 | 理由 |
|---|---|---|
| 普通日志、配置行、短文本 | Scanner | 代码短,默认按行切分 |
| 一行可能超过 64 KiB | Reader 或先配置 Scanner.Buffer | 避免 token 过长导致扫描停止 |
| 需要保留换行符或自定义分隔符 | Reader.ReadString/ReadBytes | 返回数据和分隔符语义更直接 |
| 需要知道分片、回退或缓冲状态 | Reader | 可使用 Peek、Discard、ReadSlice 等接口 |

Scanner 的限制在单条 token,而不是文件总大小
Scanner 会持续从输入中取得 token,不需要一次装入整个文件。默认 split 函数是 ScanLines,但文档给出的默认最大 token 缓冲是 MaxScanTokenSize = 64 * 1024;超长行可能让扫描不可恢复地停止。因此,读几十 GB 的许多短行并不是问题,读一条很长的 JSON 或 SQL 行才是风险。
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
func scanLines(r io.Reader) error {
scanner := bufio.NewScanner(r)
// 先设置上限,再开始 Scan;max 要覆盖单条记录和可能的换行字节。
scanner.Buffer(make([]byte, 32*1024), 2*1024*1024)
for scanner.Scan() {
// Text 会返回当前 token 的字符串副本,适合交给本轮业务处理。
line := scanner.Text()
fmt.Println(len(line))
}
// EOF 会被视为正常结束,其他读取错误由 Err 返回。
return scanner.Err()
}
func main() {
err := scanLines(strings.NewReader("短行\n另一行\n"))
if err != nil {
fmt.Println("读取失败:", err)
}
}
Buffer 必须在扫描开始前调用;它解决的是 token 上限,不会让 Scanner 变成可任意恢复的分块解析器。如果记录长度无法可靠估计,或者超过上限后还要继续处理后续内容,应该改用 Reader。
Reader 适合长记录、分隔符和更细的错误判断
Reader.ReadString(delim) 和 ReadBytes(delim) 会读到分隔符;如果输入在找到分隔符前结束,它们仍会返回已经读到的数据和错误。这个返回约定很适合处理“最后一行没有换行”的文件:先消费非空数据,再把 io.EOF 视为输入结束。
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
func readLines(r io.Reader) error {
reader := bufio.NewReaderSize(r, 64*1024)
for {
// ReadString 会保留分隔符;没有结尾换行时仍可能返回最后一段内容。
line, err := reader.ReadString('\n')
if len(line) > 0 {
fmt.Printf("记录长度=%d\n", len(strings.TrimSuffix(line, "\n")))
}
if err == io.EOF {
// 最后一条无换行记录已经处理,正常结束读取。
return nil
}
if err != nil {
// 非 EOF 错误需要交给上层决定是否重试或终止。
return err
}
}
}
如果不希望每次返回一条完整字符串,可以进一步使用 ReadSlice 或 ReadLine 逐段处理,但要牢记切片可能在下一次读取后失效;需要跨越下一次读取保存内容时,改用 ReadBytes、ReadString 或主动复制。

用四个问题完成项目里的选择
可以把最终决策固定成四个问题:第一,单条记录是否稳定小于 Scanner 的配置上限;第二,默认按行或自定义 token 是否已经足够;第三,是否需要保留分隔符、读取片段或查看缓冲区;第四,遇到半条记录和非 EOF 错误时,业务是否需要自行决定恢复方式。
四个答案都偏简单时,用 Scanner,并把 scanner.Err() 纳入收尾处理;只要超长记录、分隔符语义或错误恢复是核心需求,就用 Reader。无论选择哪一种,文件本身仍然可以按流读取,关键是不要把“总文件很大”和“单条记录很大”混为一谈。
- 短记录、固定 token:
Scanner。 - 长记录、需要分隔符或缓冲控制:
Reader。 - 边界不确定:优先选择能保留部分数据并让上层决定的读取方式。
常见问题
Scanner 读大文件一定会占满内存吗?
不会。它按 token 读取,文件总大小不是默认缓冲上限;真正需要关注的是单个 token 的长度和你设置的 Buffer 上限。
把 Scanner.Buffer 调大后就不用 Reader 了吗?
不一定。能估计上限且只需要 token 的场景可以继续用 Scanner;如果还要分段恢复、保留分隔符或控制错误处理,Reader 的接口更合适。
Reader.ReadString 遇到 EOF 要不要丢弃返回内容?
不要直接丢弃。若返回的字符串非空,应先处理最后一段,再根据 err == io.EOF 正常结束。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
262 收藏
-
426 收藏
-
438 收藏
-
190 收藏
-
270 收藏
-
262 收藏
-
239 收藏
-
417 收藏
-
312 收藏
-
191 收藏
-
124 收藏
-
284 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习