因为 operator

用 std::fstream 直接写 KV,为什么不能只靠 operator 和 <code>getline
因为没分隔符、无长度标记、类型不固定,读写会错位。比如存 "key1:value1" 后再写 "key2:val\nue2",换行符直接破坏解析;又或者 value 本身含冒号或空格,std::getline 按行切就彻底失效。
真正能跑通的底线方案是:每条记录必须自描述长度。常见做法是「4 字节长度 + 原始字节流」,不依赖文本格式。
- 写入前先用
htonl转成网络序,避免跨平台字节序不一致 - key 和 value 分开存两段,各自带长度头,否则无法区分 key 结束点和 value 开始点
- 不要用
std::endl—— 它强制 flush,性能差且可能中断二进制写入
std::fstream 打开模式选 std::ios::binary | std::ios::in | std::ios::out 的实际影响
缺 std::ios::binary 在 Windows 下会把 \x0A 自动转成 \x0D\x0A,导致读长度头时错 1 字节;只用 std::ios::out 会清空文件,没法追加;不用 std::ios::in 就没法 seekg 回溯查找——而 KV 查询必须支持随机定位。
- 首次创建文件时用
std::ios::trunc,但后续操作一律去掉,否则每次写都丢数据 - 用
seekp(0, std::ios::end)追加新 record 可以,但查 key 必须从头 scan,没有索引就只能这样 - 注意
fstream::tellg()返回std::streampos,不是size_t,比较时要转成off_t或用static_cast
查 key 时反复 seekg + read 的性能陷阱
磁盘 I/O 是最慢环节,每查一次 key 都从头扫到尾,O(n) 时间 + 大量小 read,实测 10MB 文件查末尾 key 可能触发上百次系统调用。这不是算法问题,是接口误用。
立即学习“C++免费学习笔记(深入)”;
- 一次性
read整个文件到内存(std::vector<char></char>),再用指针遍历,比反复 seek 快一个数量级 - 但要注意:文件不能大于可用内存,否则 OOM;生产环境需加 size 限制(如
if (file_size > 64 * 1024 * 1024) throw std::runtime_error("file too large");) - 遍历时用
memcpy提取长度字段,别用reinterpret_cast<int32_t></int32_t>—— 未对齐访问在 ARM 或某些 x86 设置下会 SIGBUS
删除 key 时不能真删,得用「墓碑标记」
文件流不支持中间删字节,seekp 到某位置然后 write,只会覆盖,不会收缩。如果直接覆盖为全 0,下次 scan 时可能把 0x00000000 误判成长度 0 的合法 record。
- 约定 record 开头第一个字节为 flag:
0xFF表示有效,0x00表示已删(墓碑) - 写入新 record 前,先 scan 空闲 slot(flag 为
0x00且长度头合理),复用空间;没有才追加 - compact 操作留到上层调用,引擎本身不自动 compact —— 否则每次 put 都可能触发全量重写,延迟不可控
最简可行版的边界其实很窄:不支持并发、不校验 CRC、不处理断电写半条 record。这些不是“可以以后加”,而是只要漏掉其中一条,程序运行一阵后就会静默丢数据。尤其是 length header 被截断一半的情况,后续所有解析都会偏移。



















