mmap不适合逐行扫描,因其不加速换行符查找,需手动线性搜索且触发频繁缺页中断;而bufio.scanner/reader通过缓冲预读更稳省内存,仅当行长度固定或配合索引时mmap才显优势。

直接用 mmap 做大文本“行遍历”是典型误用——它不加速找换行符,只加速已知偏移的字节访问。真要按行处理,bufio.Scanner 或 bufio.Reader 仍是更稳、更省内存的选择。
为什么 mmap.ReadAt 不适合逐行扫描
mmap 把文件变成一个 []byte 切片,但它不会帮你定位 '\n'。你要逐行读,就得自己在映射内存里反复调 bytes.IndexByte(mm, '\n'),而每次搜索都是从头开始线性扫——这和 bufio.Scanner 的底层行为没本质区别,反而绕过了缓冲区预读优化,还占着虚拟内存不放。
- 50GB 日志文件全映射后,
mm.Len()返回 50e9,但你调bytes.IndexByte(mm[0:], '\n')仍要扫到第一个换行符才停;第二行就得从mm[offset+1:]再扫一遍 - 缺页中断密集:顺序扫描会触发大量页加载,内核频繁把磁盘块搬进内存,实际比
bufio的 64KB 缓冲区预取更慢 - Windows 上易出
SIGBUS:若文件被其他进程截断,或映射后大小变化,mm[offset]直接 panic
什么场景下 mmap + 行提取才真正快
仅当行结构固定(如每行严格 512 字节)、且你能直接算出第 N 行起始偏移时,mmap 才体现价值。这时你跳过所有查找逻辑,靠下标直取。
- 日志格式为
[timestamp][level][msg]\x00... (pad to 512),查第 100 万行:mm[999999*512 : 999999*512+512],毫秒级 - 预建索引文件(
.idx),每条记录存line_number → file_offset,用mm.ReadAt(buf, offset)随机读对应行,比os.File.Seek()+Read快 2–3 倍 - 配合布隆过滤器流式解析:先 mmap 整个文件,再用
for i := 0; i 固定步长跳转,提取字段做 <code>bloom.Add([]byte(orderID)),避免bufio.ReadLine()的内存分配开销
安全使用 mmap-go 的硬性条件
别碰 syscall.Mmap。用 github.com/edsrzf/mmap-go 是底线,否则跨平台就是定时炸弹。
- 映射前必须
f.Stat()拿真实大小,别信配置里写的 “2GB”——mmap.Open("x.log")自动按文件当前长度映射,最省心 - 只读就用默认
mmap.Open();要写入(比如打标记)必须mmap.OpenFile(f, mmap.RDWR),且每次改完立刻mm.Flush() - 写入前确认文件已用
os.O_RDWR打开;想扩大文件?得先syscall.Ftruncate(int(f.Fd()), newSize),mmap不负责扩容
该用 bufio 还是 mmap?看你的“行”怎么定义
如果“行”由 '\n' 动态界定(绝大多数文本日志),bufio.Scanner 是事实标准:它用滚动缓冲区 + 预读机制,内存可控、逻辑清晰、错误处理完备。
- 需要上下文(前/后几行)、支持超长行、容忍损坏数据?
bufio.Reader.ReadLine()更灵活 - 要正则匹配、高亮、分组提取?
regexp直接喂buf,不用自己在[]byte上重实现FindStringSubmatchIndex - 真有性能瓶颈?先
pprof确认是 IO 等待还是 CPU 解析——多数时候慢在strings.Split或json.Unmarshal,不是读文件本身
复杂点在于:mmap 的优势藏在“已知位置”的随机访问里,而行遍历天然依赖“未知位置”的查找。一旦你开始在 mmap 区域里反复 bytes.IndexByte,就等于主动放弃它的核心价值。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











