mmap比fread更适合大文件关键词查找,因其通过虚拟内存映射避免系统调用与内核缓冲区拷贝,支持高效线性扫描;需正确处理\0、显式传长度、计算文件偏移,并注意缺页中断优化。

内存映射 mmap 为什么比 fread 适合大文件关键词查找
因为 mmap 把文件直接映射成内存地址,跳过内核缓冲区拷贝和用户态反复读取的开销。对 GB 级文件,fread 循环读块(比如 64KB)再扫关键词,会触发大量系统调用和内存复制;而 mmap 后,你拿到的是一个指针,KMP 可以像查普通内存一样线性扫描——CPU 缓存更友好,也更容易被编译器优化。
但要注意:mmap 不等于“把整个文件加载进物理内存”。它只是建立虚拟地址映射,实际页在首次访问时才按需调入(lazy loading),所以即使映射 10GB 文件,也不会立刻 OOM。
- 必须检查
mmap返回值是否为MAPPED_FAILED,尤其在 32 位进程或低内存机器上容易失败 - 映射时推荐用
PROT_READ+MAP_PRIVATE,避免写时拷贝和权限问题 - Windows 对应是
CreateFileMapping+MapViewOfFile,不是mmap,跨平台别硬套
KMP 在内存映射文件上的正确初始化方式
标准 KMP 的 next 数组只依赖模式串,和文本无关,这点不变。但很多人忽略:模式串本身不能含 \0(C 风格字符串终止符),否则 strlen 截断,next 构建出错——而大文件里二进制内容完全可能含 \0。
所以 KMP 查找函数必须接受显式长度参数,不能依赖 \0 结尾:
立即学习“C++免费学习笔记(深入)”;
int kmp_search(const char* text, size_t text_len, const char* pattern, size_t pat_len) {
if (pat_len == 0) return 0;
vector<int> next = build_next(pattern, pat_len);
// ... 实现略
}-
text_len必须传st.st_size(stat获取的真实文件大小),不能用strlen - 如果模式串来自用户输入(如命令行参数),要确保没额外截断或编码转换(比如 UTF-8 中文当单字节处理就错)
- 构建
next时,循环上限是pat_len,不是strlen(pattern)
查到结果后如何定位真实文件偏移?
mmap 返回的指针 addr 和文件起始位置一一对应,所以匹配到的地址 found_ptr 减去 addr 就是文件内的字节偏移。
但容易错在:忘了 mmap 可能从文件某 offset 开始映射(比如只映射中间一段),这时得加上 offset 参数才是真实位置。
- 若全文件映射:
file_offset = found_ptr - addr - 若部分映射(如
mmap(..., len, PROT_READ, MAP_PRIVATE, fd, 1024)):file_offset = 1024 + (found_ptr - addr) - 打印时用
%zu格式化size_t偏移,别用%d或%ld(32/64 位不一致)
性能瓶颈往往不在 KMP,而在 mmap 的 page fault 和 I/O 调度
真正跑慢的时候,不是 KMP 循环慢,而是第一次遍历映射区域时触发大量缺页中断(page fault),内核要从磁盘读页进内存。这时候 CPU 在等 I/O,perf record 会看到高比例的 page-faults 和 iowait。
解决方法不是换算法,而是预热:
- 用
madvise(addr, len, MADV_WILLNEED)提示内核“马上要读”,触发异步预读 - 对超大文件(>4GB),考虑分段
mmap+ 多线程搜索,避免单次映射过大导致mmap失败或 swap 压力 - 关闭 swap(
swapon --show检查)或设vm.swappiness=1,防止映射页被换出
最常被忽略的是:没检查 mmap 是否真的成功映射了全部范围——len 超过 size_t 最大值、文件被截断、或者 fd 已关闭,都会让后续指针运算变成野指针。










