断点续传需用二进制模式+flush后tellp()获取真实偏移,独立存于.offset文件,并启动时校验offset≤文件长度,超则重置为文件长度。

用 std::ofstream::tellp() 记录写入位置,但要注意它不是实时的
断点续传写入的核心是:每次写入前知道上一次停在哪,然后从那个字节继续。很多人直接在写几行后调用 tellp(),发现返回值没变——因为 std::ofstream 默认带缓冲,tellp() 返回的是“文件指针逻辑位置”,不是磁盘实际偏移,更不等于已刷盘字节数。
- 必须在关键节点手动刷新:
file.flush()后再调用tellp(),否则拿到的是缓冲区里的“假位置” - 如果写入量小、频率高,频繁
flush()会严重拖慢性能;建议每写入 4KB–64KB 刷一次,或在断点保存前强制刷一次 - 注意:Windows 下以文本模式(默认)打开文件时,
\n会被转成\r\n,导致tellp()返回值和实际字节数不一致 → 务必用二进制模式:std::ios::binary
断点位置该存哪?推荐单独写一个 .offset 文件
不能把偏移量硬编码进主文件头或末尾——主文件可能被其他程序读写、截断、覆盖,一动就错。独立存储最稳妥,且可原子更新。
- 写入主文件前,先读取
data.bin.offset得到上次位置;打开主文件时用std::ios::in | std::ios::out | std::ios::binary,再用seekp(offset)定位 - 每次成功写入一批数据并
flush()后,立刻用另一个std::ofstream(加std::ios::trunc)把新tellp()值写进.offset文件,写完立即close() - 避免用
fopen+fwrite混用:C++ 流和 C 文件指针共享内核 fd 时行为未定义,尤其在多线程下容易错乱
崩溃后如何校验 .offset 是否可信?靠文件长度兜底
进程崩溃时,可能出现“.offset 已更新,但主文件没写完”的情况。单纯信任 offset 文件会越写越乱。
- 启动时,先用
std::filesystem::file_size("data.bin")获取当前真实长度 - 再读
data.bin.offset,如果 offset > 文件长度,说明上次写入失败,应重置 offset = 文件长度(即追加写) - 如果 offset
- 别用
stat()手动解析,std::filesystem::file_size在 C++17+ 中跨平台可靠,且不会因权限/符号链接出错
多线程写同一个文件?别碰,改用单写线程 + 队列
std::ofstream 不是线程安全的,多个线程共用一个对象调用 write() 或 tellp(),结果完全不可预测 —— 可能位置错乱、写入重叠、tellp() 返回随机值。
- 真正可行的做法:所有写请求发给一个专用写线程(比如用
std::queue<:vector>></:vector>+std::mutex),由它串行执行write()→flush()→tellp()→ 更新 offset 文件 - 如果必须多线程,每个线程写不同文件(如分片),最后合并;不要共用一个流对象
- 别试图用
std::shared_mutex锁住整个流对象 ——tellp()和write()中间存在时间窗口,锁不住逻辑一致性
最常被忽略的一点:tellp() 的返回值类型是 std::streampos,它在某些平台(如 32 位环境或老编译器)可能是 32 位整数。超过 2GB 的文件续传会溢出。务必检查 sizeof(std::streampos),生产环境建议强制用 64 位系统或显式 cast 到 uint64_t 存储 offset。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











