mmap比os.Open+io.Read更适合读大文件,因其将文件直接映射进虚拟内存,跳过内核缓冲区拷贝和用户态内存分配,按需缺页加载,10GB文件仅需几KB内存,随机访问近乎零延迟;而后者seek远端需重读或预加载,内存占用线性增长。

为什么 mmap 比 os.Open + io.Read 更适合读大文件
因为 mmap 把文件直接映射进虚拟内存,跳过内核缓冲区拷贝和用户态内存分配,读取时按需触发缺页中断——10GB 文件只需几 KB 内存开销,且随机访问(如跳转到 offset 8.2GB)几乎无延迟。而 os.Open + io.Read 顺序读还好,一旦 seek 到远端位置,就得重新 read 或预加载,内存占用线性增长。
用 syscall.Mmap 正确打开只读映射
Go 标准库没封装 mmap,得用 syscall.Mmap。注意它不接受 *os.File,必须传底层文件描述符 int;且 Windows 和 Unix 行为不同,Linux/macOS 可用 syscall.PROT_READ + syscall.MAP_PRIVATE,Windows 需用 syscall.MEM_COMMIT | syscall.PAGE_READONLY。
- 先调用
f.Fd()获取 fd,确保文件以os.O_RDONLY打开 - 用
stat.Size()算出映射长度,不能传 0 或超过文件大小,否则syscall.EINVAL - 映射起始 offset 必须是页对齐的(通常 4096 字节),用
offset &^ (pageSize - 1)向下对齐 - 返回的
[]byte是可直接索引的切片,但底层内存由 OS 管理,unsafe.Slice或强制转换要格外小心
避免 SIGBUS:访问越界或文件被截断
syscall.Mmap 成功后,若文件被其他进程 truncate 或删除,后续访问超出当前文件大小的地址会触发 SIGBUS(Go 进程直接崩溃)。这不是 Go 的 panic,无法 recover。
- 映射前加
f.Stat()记录初始大小,之后所有访问都用这个快照值做边界检查 - 不要依赖
len(mmapSlice)当真实长度——它反映的是 mmap 调用时传入的length,不是当前文件大小 - 若需长期持有映射且文件可能变动,考虑用
syscall.Msync同步后主动syscall.Munmap,再重新映射
跨平台兼容写法:用 golang.org/x/sys/unix 替代 syscall
syscall 包在 Go 1.17+ 已弃用,且 Windows 的 syscall 和 Unix 的语义差异大。推荐统一用 golang.org/x/sys/unix(Unix/Linux/macOS)和 golang.org/x/sys/windows(Windows)。
立即学习“go语言免费学习笔记(深入)”;
例如 Linux 下:
data, err := unix.Mmap(int(f.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_PRIVATE)
Windows 下则需先
windows.CreateFileMapping + windows.MapViewOfFile,返回的是 uintptr,要用 unsafe.Slice 转成 []byte,且长度必须显式传入——这点最容易漏,导致越界读。
页对齐、信号安全、跨平台释放逻辑这些细节,不手写一遍根本记不住。尤其 Munmap 忘调会导致内存泄漏,而 Go 的 GC 对 mmap 内存完全无感知。


















