bufio.Scanner默认64KB缓冲区遇超长行报错,需调用scanner.Buffer扩容;大文件禁用ReadFile,应依场景选Scanner、Reader或流式解析器;Reader.Read须检查io.EOF;mmap跨平台差且未必更快;顺序读勿滥用O_DIRECT。

用 bufio.Scanner 逐行读取时内存不爆但容易丢数据
默认情况下 bufio.Scanner 的缓冲区只有 64KB,遇到超长行(比如单行 JSON、日志中带大段 base64)会直接报错 scanner: token too long。这不是性能问题,是安全限制,但很多人误以为是“读得慢”。
解决方法是手动扩容缓冲区:
scanner := bufio.NewScanner(file) buf := make([]byte, 0, 64*1024) // 初始 64KB,动态增长 scanner.Buffer(buf, 10*1024*1024) // 最大允许 10MB 行长
注意第二参数不能设为 math.MaxInt32 —— 某些系统调用会因过大的值返回 EINVAL。
真正的大文件(GB 级)别用 ioutil.ReadFile 或 os.ReadFile
这两个函数会把整个文件一次性加载进内存,哪怕文件只有 500MB,也极可能触发 OOM 或让 GC 压力陡增。Golang 进程 RSS 突然飙高、卡顿几秒,往往就是这个原因。
立即学习“go语言免费学习笔记(深入)”;
替代方案取决于你要做什么:
- 只统计行数或简单匹配?用
bufio.Scanner+ 自定义分隔符 - 需要随机访问某几行?先用
bufio.Reader配合Seek定位,再读小块 - 要解析 CSV/JSONL?用流式解析器(如
encoding/csv的csv.NewReader,或jsonl包)
bufio.Reader.Read 手动控制读取粒度更灵活但易出错
当 Scanner 不够用(比如按固定字节块处理、跳过 BOM、处理粘包式二进制格式),就得退到 bufio.Reader。它的 Read 方法返回实际读到的字节数,必须检查 err == io.EOF 而非仅靠返回长度判断结束。
【极品模板】出品的一款功能强大、安全性高、调用简单、扩展灵活的响应式多语言企业网站管理系统。 产品主要功能如下: 01、支持多语言扩展(独立内容表,可一键复制中文版数据) 02、支持一键修改后台路径; 03、杜绝常见弱口令,内置多种参数过滤、有效防范常见XSS; 04、支持文件分片上传功能,实现大文件轻松上传; 05、支持一键获取微信公众号文章(保存文章的图片到本地服务器); 06、支持一键
常见错误写法:
for {
n, err := reader.Read(buf)
if n == 0 { break } // ❌ 错!n==0 不代表结束,可能是临时阻塞或空行
// ...
}
正确写法:
for {
n, err := reader.Read(buf)
if n > 0 {
// 处理 buf[:n]
}
if err == io.EOF {
break
}
if err != nil {
// 处理其他错误(如 io.ErrUnexpectedEOF)
break
}
}
Linux 下用 mmap 不一定更快,且跨平台差
有人会想到用 golang.org/x/sys/unix.Mmap 做内存映射。它在某些场景(如反复随机读同一块大文件)确实减少拷贝,但代价明显:
- Windows 不支持,
syscall.Mmap在 Windows 上行为不同 - 映射后仍需手动管理
Munmap,漏掉会导致资源泄漏 - 对 SSD 友好,但对 NFS 或 FUSE 文件系统可能反而变慢
- Go 的 GC 不感知 mmap 内存,可能导致 RSS 报告失真
除非你明确压测对比过,并确认瓶颈真在内核拷贝而非业务逻辑,否则优先用 bufio + 合理 buffer size。
最常被忽略的一点:文件打开时的 flag。如果只是顺序读,加上 os.O_RDONLY | syscall.O_DIRECT(Linux)或 syscall.FILE_FLAG_NO_BUFFERING(Windows)看似能绕过 page cache,但实际会显著降低吞吐——因为失去了预读和合并 IO 的优势。普通场景老老实实用默认打开方式就行。









