c++oding="utf-8" ?>
最简单可靠的方式是用 std::ofstream 以 std::ios::app | std::ios::binary 模式打开文件,自动定位末尾追加,避免清空、换行符转换和覆盖风险;需配合缓冲读写、std::filesystem::path 处理路径、临时文件+原子重命名保障崩溃安全。

用 std::ofstream 以 std::ios::app 模式追加写入最简单可靠
直接打开目标大文件时加上 std::ios::app 标志,每次写入都会自动定位到末尾,不用手动 seekp,也避免多线程或中断导致的覆盖风险。Windows 和 Linux 下行为一致,是 POSIX 兼容做法。
常见错误是误用 std::ios::out 单独打开(会清空原文件),或漏掉 std::ios::binary 导致文本模式换行符被悄悄转换(尤其跨平台合并二进制文件时)。
- 必须同时指定
std::ios::app | std::ios::binary - 源文件也建议用
std::ios::binary读取,防止内容截断(比如遇到\0) - 目标文件首次创建时,
app模式会自动新建,无需提前 touch
小文件太多时,std::ifstream::read + std::ofstream::write 批量搬运比逐字节快得多
用 char 缓冲区一次性读写 64KB~1MB 数据,能显著降低系统调用次数。小于 4KB 的缓冲区在多数 SSD 上已无明显收益;超过 2MB 则内存占用上升,且缓存局部性变差。
典型场景:合并几百个日志碎片、分片下载的 zip 分卷、或构建资源包。若源文件平均大于 10MB,缓冲区可设为 1MB;若全是几 KB 的配置文件,64KB 更稳。
- 缓冲区用
std::vector<char> buf(1024 * 64)</char>分配,避免栈溢出 - 每次
read()后检查gcount(),而非eof()—— 后者只在尝试读失败后才置位 - 写入前确认
write(buf.data(), n)中n等于gcount(),否则可能写入脏内存
Windows 下路径含中文或空格时,std::filesystem::path 能绕过 C 运行时编码陷阱
直接传 raw string 给 std::ifstream 构造函数,在 MSVC 默认窄字符环境下会把 UTF-8 路径误当作 GBK 解码,导致打开失败并报错 No such file or directory。用 std::filesystem::path 包一层,底层自动调用 CreateFileW。
注意:C++17 起才支持,且 MSVC 需 /std:c++17;GCC/Clang 需链接 -lstdc++fs(部分旧版本)。
- 构造方式:
std::ifstream f(std::filesystem::path("D:/日志/2024-01.txt")) - 不要对
path.c_str()做任何 reinterpret_cast,它返回的是宽字符指针 - Linux/macOS 下该写法也兼容,但无实际增益
合并中途崩溃?用临时文件 + 原子重命名规避数据损坏
直接往目标文件追加,一旦程序崩溃或断电,大文件很可能处于中间状态:部分内容写入,但长度不全、校验失败。更安全的做法是始终写入临时文件(如 bigfile.dat.part),全部写完再原子替换。
Windows 上用 std::filesystem::rename 替换已有文件是原子的(同卷前提下);Linux/macOS 同样成立。但不能用 remove + rename 组合——两步非原子。
- 临时文件名建议带 PID 或时间戳,避免并发冲突:
bigfile.dat.part_$$ - 写完后调用
std::filesystem::rename(temp_path, final_path) - 若最终路径已存在,
rename在 C++17 中会直接覆盖(POSIX 行为),无需额外判断
真正麻烦的是跨文件系统移动(比如从 /tmp 写到 /home),此时 rename 会退化为拷贝+删除,失去原子性。这种情况下,得靠外部锁文件或进程信号协调,不是单次 write 能解决的。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











