用 fseek + fread 按字节偏移模拟分页,避免逐行扫描;需二进制模式打开文件,跳转后向后找 '\n' 对齐行首;为稳定页行数,应预建每页起始偏移索引表并支持增量更新。

用 fseek + fread 按字节偏移模拟分页,别用 std::getline 逐行扫
大文本文件(几 GB)没法全读进内存,更不能靠 std::getline 从头一行行遍历到第 N 页——那等于做 N×页大小的无效 IO。核心思路是:把文件当字节数组,用 fseek 直接跳到估算的起始偏移,再向后找第一个完整行首。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先用
fstat或GetFileSizeEx拿到总大小,除以目标页行数,粗略算出每页对应字节数(比如 10 万行 ≈ 5MB) - 用
fseek(fp, offset, SEEK_SET)跳转,但必须向后搜索最近的'\n',否则可能切在行中间 - 跳转后调用
fgetc循环读,直到遇到'\n'再开始真正读取一页内容(丢弃跳转点前半行) - Windows 下注意文本模式会把
\r\n当作一个字符处理,务必用二进制模式打开:fopen("log.txt", "rb")
如何保证每页行数稳定?用预扫描 + 缓存边界偏移表
纯靠字节估算翻页,页末尾常出现“多一行”或“少一行”,尤其日志类文件行长短差异大。稳定分页得提前建一张“每页起始偏移”索引表,而不是每次临时计算。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 首次加载时顺序扫描一次文件,只记录每 10 万行的首个
'\n'后位置(即下一行开头),存进std::vector<size_t></size_t> - 后续翻页直接查表:
fseek(fp, offsets[page_no], SEEK_SET),再读指定行数 - 索引表可序列化到小文件(如
file.idx),下次启动直接 mmap 加载,避免重复扫描 - 如果文件会被追加写,索引需标记“是否过期”,追加超过阈值就触发增量重扫(只扫新增部分)
内存不够时,mmap 比 fread 更省心但要注意平台差异
mmap 让 OS 管理页面换入换出,代码简洁,但 Windows 的 CreateFileMapping + MapViewOfFile 行为和 Linux 差异明显,容易卡在权限或映射大小上。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- Linux 下优先用
mmap:支持超大文件(off_t)、按需加载、共享映射节省内存 - Windows 下注意:
CreateFileMapping第二个参数不能全 0(表示 4GB 上限),大文件必须拆成HANDLE+ 高/低 32 位偏移 - 无论哪边,都别对整个文件
mmap—— 改用mmap映射当前页附近 2–3MB 区域,翻页时munmap+ 重新mmap - 用
msync/FlushViewOfFile前确认是否真需要写回,只读场景完全不用
分页读取后怎么快速定位某一行?别现场解析,建行号→偏移哈希表
用户点击“跳转到第 1234567 行”,如果每次都要从头 fseek + 扫描,延迟不可接受。必须把行号和文件偏移的映射关系缓存下来。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 扫描建索引时,每读完 1000 行就记一条
{line_num: offset}到std::unordered_map或排序数组(后者可用二分查找) - 查询时先在内存索引里二分找到最接近的目标行号,再
fseek过去,最多再读几行就能精确定位 - 键用
int64_t存行号,值用off_t存偏移,别用std::string拼接路径或行内容——那是内存杀手 - 索引内存占用大?改用稀疏索引(每万行记一次),配合小范围线性扫描,平衡内存与速度
真正的难点不在怎么跳,而在怎么让“跳”不破坏流式体验:偏移估算不准、换行符跨页、文件被外部修改、索引与文件状态不同步……这些细节不处理,分页就只是把 crash 换成了卡顿。



















