mmap不等于“自动变快”,因其仅建立虚拟内存映射,真正提速需满足随机读、大文件、页缓存失效等前提;顺序扫描时bufio.reader配合64kb缓冲区通常更快更稳。

为什么 mmap 不等于“自动变快”
mmap 本身不提速,它只是把文件映射进虚拟内存地址空间,让 read() 系统调用变成指针偏移访问。真正快起来的前提是:你正在做大量非顺序、跳来跳去的随机读(比如查索引、解析固定 offset 的二进制结构),且文件足够大、OS 页缓存已失效。如果只是从头到尾扫一遍,bufio.Reader 配合 64KB 缓冲区通常更快、更稳。
用 github.com/edsrzf/mmap-go 做安全映射
Go 标准库没封装 mmap,得靠第三方库。github.com/edsrzf/mmap-go 是目前最成熟的选择,跨平台支持好,API 清晰。关键不是“怎么映”,而是“怎么不崩”:
- 必须用
os.O_RDONLY打开文件,写入映射区会 panic - 映射后要检查返回的
[]byte长度是否匹配预期,避免截断文件被误读 - 不要在多个 goroutine 里同时读同一段映射内存——Go 不保证缓存一致性,需自行加锁或用
atomic控制访问 - Windows 上映射失败常见原因是文件句柄权限不足,建议显式传
syscall.FILE_MAP_READ
示例片段:
file, _ := os.Open("index.dat")
defer file.Close()
mmap, err := mmap.Map(file, mmap.RDONLY, 0)
if err != nil {
log.Fatal(err)
}
defer mmap.Unmap() // 必须调用
data := mmap.Bytes() // 直接当 []byte 用
随机读时如何避免 SIGBUS 和越界 panic
最常见的崩溃是读到文件末尾之后的位置,或者文件被其他进程 truncate。mmap 不会自动感知这些变化:
- 每次读前先确认 offset + length ≤ len(
mmap.Bytes()),别信“文件没动就安全” - 对不确定长度的字段(比如字符串结尾不固定),不要直接
unsafe.Slice,改用边界检查 +bytes.IndexByte安全扫描 - 遇到
SIGBUS,99% 是读了未映射页——要么映射时用了mmap.INIT但没预加载,要么文件被截断;加defer func(){ if r := recover(); r != nil { ... } }()捕获不现实,应提前防御
比 mmap 更轻量的替代方案:预读 + Seek 复用
如果你的“随机读”其实是有规律的(比如每 4KB 读一个 header,或按固定 stride 跳),完全没必要上 mmap。用普通 *os.File + Seek + Read 更可控:
- 打开时加
syscall.O_DIRECT(Linux)绕过页缓存,减少内存污染 - 复用同一个
[]byte切片,避免频繁分配,例如buf := make([]byte, 4096)循环用 - 用
file.Seek(offset, io.SeekStart)定位,再io.ReadFull(file, buf)确保读满,避免部分读逻辑错乱 - 实测在 SSD 上,这种模式比 mmap 启动更快、错误更明确、GC 压力更低
真正需要 mmap 的场景极少:只读、超大(>10GB)、随机访问密度高、且格式高度结构化(如数据库 WAL、列存索引)。其它情况,先压测 Seek+Read,别一上来就 mmap。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











