mmap 读超大文本文件通常不如 bufio.Scanner 高效,易触发 SIGBUS、缺页抖动和内存耗尽;仅在固定结构二进制或定长 ASCII 行且有索引定位时才提速。

别用 mmap 读“超大字符串文件”——除非你明确知道每行偏移、固定长度,或已有索引定位到字节位置。否则它不会比 bufio.Scanner 快,反而更容易触发 SIGBUS、缺页抖动和虚拟内存耗尽。
为什么 mmap.ReadAt 对文本搜索几乎没用
很多人以为把 50GB 日志文件 mmap 进来,再用 bytes.Index 扫一遍就能加速“找某段字符串”,实际完全相反:
-
mmap不预加载数据,随机访问会密集触发缺页中断,磁盘 IO 反而更碎 - 全文扫描时,
bufio.Scanner的 4KB~64KB 缓冲已足够减少系统调用,且内存局部性更好 -
bytes.Index在整块[]byte上暴力扫,和scanner.Text()+strings.Contains时间复杂度一样,但前者绕过 GC 管理,越界就崩 - 日志含 UTF-8、换行不齐、空行、编码异常?
mmap返回的是裸字节,你得自己处理bytes.Split边界,极易切错行
什么场景下 mmap 真正提速
只在满足以下全部条件时,mmap 才比 os.File.ReadAt 明显快:
- 文件是**固定结构二进制**(如每条记录 1024 字节)或**纯 ASCII 行定长**(如每行正好 512 字节)
- 你已通过外部索引(B+Tree、布隆过滤器、预建 offset 表)直接算出目标记录的
offset - 访问模式是稀疏跳转(比如查第 32768 行、第 999999 条),而非顺序遍历
- 使用
github.com/edsrzf/mmap-go,且映射块大小控制在 64MB 内,每次只映射当前所需段
示例:查固定行长日志的第 n 行
立即学习“go语言免费学习笔记(深入)”;
mm, err := mmap.Open("log.bin", os.O_RDONLY, 0)
if err != nil {
panic(err)
}
defer mm.Unmap()
<p>lineLen := 512
offset := int64(n) * lineLen
data := make([]byte, lineLen)
copy(data, mm.Bytes()[offset:offset+int64(lineLen)]) // 直接下标,毫秒级容易被忽略的 mmap 生存期陷阱
mmap.Map 或 mmap.Open 返回的对象不是 Go 值,它背后是操作系统分配的虚拟内存页,GC 完全不感知:
-
defer mm.Unmap()只在函数退出时释放——若中间return或 panic,必须手动补mm.Unmap() - 对
mm.Bytes()做子切片(如mm.Bytes()[100:200])后传给其他 goroutine,该切片失效后原映射仍驻留,可能撑爆地址空间 - Windows 下文件被 Excel 占用、路径含中文、权限不足,
mmap.Open直接失败,错误信息是The parameter is incorrect,不是权限问题 - 映射后文件被
truncate或unlink,后续访问对应 offset 会立即SIGBUS,无任何 warning
真正要安全读超大文本文件,优先用 bufio.NewReaderSize(f, 64*1024) 配合 io.ReadFull 按块读;只有当你能跳过解析、直接按字节偏移拿数据时,mmap 才值得上。



















