高效处理大型二进制日志应使用mmap直接映射文件到内存:linux/macos用mmap,windows用createfilemapping+mapviewoffile;需检查返回值、正确释放(munmap)、严格对齐结构体、按实际文件大小而非字符串函数判断边界、避免悬空指针,并校验磁盘布局与内存布局一致性。

用 mmap 替代 new 或 fread 直接映射文件到内存
处理大型二进制日志(比如几个 GB 的 access.log.bin)时,用 new 分配缓冲区或循环 fread 读块,既慢又容易出错——内存拷贝多、边界难控、std::vector<uint8_t></uint8_t> 容易触发多次重分配。真正高效的做法是绕过用户态缓冲,让操作系统把文件直接映射成一块可读的虚拟内存。
Linux/macOS 下用 mmap,Windows 下用 CreateFileMapping + MapViewOfFile。以 Linux 为例:
#include <sys>
#include <fcntl.h>
#include <unistd.h><p>int fd = open("log.bin", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
uint8_t<em> data = static_cast<uint8_t>>(mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
// data 现在可像普通指针一样访问:data[0], data[1024], <em>(LogEntry</em>)(data + offset)
</uint8_t></em></p></unistd.h></fcntl.h></sys>
- 必须检查
mmap返回值是否为MAP_FAILED,否则后续解引用会段错误 - 映射后记得
close(fd),mmap不依赖 fd 存活,但不关会泄露句柄 - 不要对
data调用delete[],要用munmap(data, size)
结构体指针偏移需严格对齐,避免 memcpy 误读字段
日志通常是固定格式结构体连续排列(如 struct LogEntry { uint64_t ts; uint32_t pid; char msg[256]; };),直接用 (LogEntry*)data + i 访问第 i 条,前提是结构体没被编译器悄悄加填充。一旦 msg 前有未对齐字段,sizeof(LogEntry) 就不等于实际磁盘布局大小。
- 用
#pragma pack(1)或[[gnu::packed]]强制紧凑布局(GCC/Clang) - 更稳妥的是按字节偏移计算地址:
auto entry = reinterpret_cast<logentry>(data + i * expected_stride)</logentry>,其中expected_stride来自协议文档或已知写入逻辑 - 绝对不要用
memcpy(&entry, data + offset, sizeof(entry))再解包——多一次拷贝,且若结构含指针或虚函数会彻底失效
遍历时避免越界:用 st_size 而非 strlen 或 feof
二进制日志不含字符串终止符,strlen((char*)data) 会一路扫到随机内存甚至崩溃;feof 在流式读取中本就不可靠,更不适用于 mmap 场景。
- 有效数据长度就是
sb.st_size,每条记录长度固定时,最大索引为(sb.st_size - header_size) / record_size - 如果日志含变长字段(如末尾
uint32_t len; char payload[];),必须从头解析:先读len,再跳过sizeof(len) + len字节,反复直到偏移 ≥sb.st_size - 任何指针算术(如
data + offset)前,务必断言offset ,调试期加 <code>assert,发布版用分支预测友好的条件判断
多线程读取要小心:mmap 区域可共享,但别让指针悬空
多个线程同时读同一块 mmap 内存是安全的(只读),但常见陷阱是:主线程 munmap 了,其他线程还在用 data 指针——立刻 SIGSEGV。
- 确保生命周期管理明确:谁
mmap,谁负责munmap;推荐封装成 RAII 类(如MappedFile),构造映射,析构解映射 - 不要把
data指针存到全局或跨线程传递后再解引用,除非你 100% 控制其生存期 - 如果日志文件可能被外部进程追加,
mmap不会自动反映新增内容——需要重新open+fstat+mremap(Linux)或重新映射
最易被忽略的是结构体打包和运行时偏移校验——协议变更、跨平台编译、甚至不同编译器版本都可能导致 sizeof(LogEntry) 和磁盘实际布局不一致,上线前务必用已知样例文件做二进制 dump 对比。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











