应根据访问模式选择:顺序遍历用bufio.reader更稳省内存,随机偏移定位或多进程共享时才用mmap;需校验文件大小、权限匹配、分块映射并显式flush,避免越界访问或刷盘丢失。

别用 io.ReadAll 加载超长文本,也别一上来就 mmap 整个文件——90% 的“大文本处理慢”问题,根源在访问模式和映射粒度没对齐。
什么时候该用 mmap,什么时候该用 bufio.Reader?
mmap 不是万能加速器,它只在特定访问模式下有优势。顺序扫描整个日志或 CSV 文件时,bufio.NewReader 配合 Read 或 ReadString('
') 更稳、更省内存、更少 TLB 压力;而当你需要频繁跳转读取固定偏移(比如按行号查第 1234567 行、按关键词 hash 定位段落),mmap 才真正发挥价值。
- 适合 mmap:随机访问、偏移定位、多进程共享只读视图(如索引文件)
- 不适合 mmap:顺序遍历 >1GB 文本、单行超长(>1MB)且需全文解析、内存受限(32 位或容器内存配额紧)
-
bufio.Scanner要小心:默认缓冲区 64KB,遇到超长行会 panic "scanner token too long",得手动ScanBytes()或调bufio.NewReader+ReadLine
用 github.com/edsrzf/mmap-go 映射文本文件的实操要点
直接调 syscall.Mmap 在 Windows 上必然失败,在 Linux/macOS 上极易因页对齐、长度超限或权限错导致 invalid argument panic。必须用封装库,且注意几个关键动作:
- 打开文件必须匹配权限:
os.Open只读 →mmap.RDONLY;os.OpenFile(f, os.O_RDWR, 0)→mmap.RDWR,混用必崩 - 映射前务必
f.Stat().Size()拿真实长度,别硬写1024 * 1024 * 1024—— 文件可能只有 200MB,内核拒绝映射超长区域 - Windows 下路径含中文或 >260 字符,得启用
\?前缀,否则mmap.Open直接返回access denied -
mm.Bytes()返回的切片已正确设置len/cap,可直接下标访问,但不能append或重切(会 panic)
分块 mmap 比全量 mmap 更安全且可控
映射几十 GB 文件虽能成功,但虚拟地址空间可能被撑满(尤其 32 位环境),且随机跳读会触发密集缺页中断,磁盘抖动严重。更合理的是按逻辑块动态映射:
- 用
os.Stat获取总大小,按 64MB 或 128MB 切分逻辑区间 - 每次只
mmap.Map当前需访问的块,处理完立刻mm.Unmap() - 避免缓存整个
[]byte,改用copy(dst, mm[offset:offset+n])局部读取 - 若需跨块拼接(如一行横跨两个块),在边界处做一次小范围重叠映射,而非加载整块
写入后不 Flush,等于没写
用 mmap.RDWR 修改内存后,数据只驻留在 page cache,断电或崩溃即丢失。defer mm.Close() 或 defer mm.Unmap() 只释放映射,不刷盘。
- 关键写入后必须显式调
mm.Flush(),它等价于msync(MS_SYNC) - 频繁
Flush()会拖慢性能,建议批量修改后统一刷一次 - 若只是临时缓存(如解压中间态),需私有映射(
MAP_PRIVATE),但mmap-go不暴露 flag 控制,得退回syscall.Mmap自行构造 - SIGBUS 最常见原因:访问了未映射区域(如 offset 越界)、文件被截断、磁盘满
真正难的不是怎么 mmap,而是判断哪段文本值得 mmap —— 偏移是否稳定、访问是否稀疏、内存是否允许预留虚拟地址空间。这些决策点比代码本身更容易被忽略。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











