用 std::map + 制表符分隔的文本序列化实现轻量键值持久化:内存用 std::map 管理,落盘时每行写为 key value,避免 value 含等号导致解析错误。

用 std::map + 文本序列化实现最简键值持久化
不依赖第三方库、不启动服务,就是读写一个本地文件,存几条 key=value。核心思路是:内存用 std::map<:string std::string> 管理,落盘时逐行写成 key value(用制表符分隔,比等号更安全,避免 value 含 = 时解析错位)。
常见错误是直接用 operator 写 std::string,但没处理换行、制表符、反斜杠等特殊字符——结果一存带
的 value,下次就解析错行。必须做转义:
- 写入前对
key和value分别调用转义函数:把→\t,→\n,\→\\ - 读取时按行分割后,再反向还原这些转义(注意顺序:先还原
\\,再\n,再\t,否则会套娃) - 文件编码默认用 UTF-8,不指定 locale,避免 Windows 下
ofstream默认 ANSI 导致中文乱码
用 std::fstream 安全覆盖写入,避免崩溃丢数据
直接 std::ofstream f("db.txt"); f 看似简单,但进程崩溃或断电时,原文件可能被清空、新内容只写一半。正确做法是「写临时文件 + 原子重命名」:
- 生成临时路径,如
db.txt.tmp_$$($$替换为当前进程 ID) - 用
std::ofstream打开临时文件,设置f.exceptions(std::ios::failbit | std::ios::badbit),确保写失败立刻抛异常 - 全部写完后,调用
std::filesystem::rename("db.txt.tmp_1234", "db.txt")(C++17 起支持) - 如果 rename 失败(比如跨磁盘),则 fallback 到手动 copy + delete,但概率极低
注意:Windows 下 rename 要求目标文件不存在,所以得先 remove("db.txt") 再 rename;Linux/macOS 允许覆盖,可省一步。
加载时跳过空行、注释和格式错误行
用户手改配置文件很常见,不能因某一行少个 就整个加载失败。解析逻辑要宽容:
- 用
std::getline()逐行读,line.empty() || line[0] == '#'直接跳过 - 用
line.find(' ')找第一个制表符位置,没找到就跳过该行(不报错) - 取
key = line.substr(0, pos),value = line.substr(pos+1),再分别 unescape - 如果 unescape 过程出错(比如
\z无法识别),把该 value 当原始字符串保留,不中断后续行
这样即使文件末尾多了一行乱码,也不影响前面 99 条正常数据的加载。
性能瓶颈在频繁刷盘?用脏标记 + 延迟写入
每次 set("a", "1") 都写磁盘,几百次操作就卡住。加一层缓存控制:
- 内部维护一个
bool dirty = false标记,set()/del()后置为true - 提供
save()显式刷盘,以及~DB() { if (dirty) save(); }保底 - 不推荐定时自动 save(比如每 5 秒),因为 C++ 没可靠后台线程机制,且用户未必需要
- 如果真要高频写,可改用追加模式(append-only log),但解析就得从头扫文件——简单场景没必要
真正容易被忽略的是:std::filesystem::rename 在某些 NFS 或容器挂载卷上可能不原子,此时应检测是否同设备(statfs),否则 fallback 到 copy + delete,并接受短暂的「两份数据」窗口期。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











