没有现成的sqlite3或shelve,因为只有三种场景需要实现B+树的持久性:准确控制页面布局,教学理解写作逻辑,连接特定的二进制格式;sqlite3更强大,支持并发事务。为什么不现成呢?
sqlite3 或 shelve?
直接用 sqlite3 或 shelve 做键值存储,99% 场景已经足够了。实现自己。 B+ 树木持久化通常只有三种情况:需要准确控制页面大小和磁盘布局(如嵌入式日志索引)、教学理解 B+ 树木写盘逻辑,或连接特定的二进制格式(例如) WAL + 页面缓存)。否则,sqlite3 的 B+ 树引擎更强大,支持事务和并发。
struct 包装页面结构时,字节序和对齐必须明确指定
B+ 树持久化的核心是将内存节点序列化为固定长度页写入文件,struct 这是最轻量级的选择,但默认行为很容易踩坑:
-
struct.pack('i', x)使用本机字节序,跨平台读写会出错;必须使用'<i'(小端)或'>i'(大端)显式声明 - 可以在结构体字段之间填充字节,例如
'iH'在 x86_64 上实际占 8 字节(因H对齐到 2 字节边界);应加'='表示标准尺寸,无填充:'=iH' - 字符串字段不能直接使用 pack,需先
encode('utf-8')并切断或补零,例如key.encode('utf-8')[:255].ljust(256, b'\0')
许多初学者试图在页面上保存一个“键偏移数组”。结果发现,插入新键后,所有偏移都必须重新计算,性能崩溃。正确的方法是让每个键占据一个固定的槽位置:
- 定义页的大小为 4096 字节,其中之前 8 字节存元信息(页面类型、键数、父页号等)
- 剩余空间分为 N 例如,每个槽位都有固定的长槽位置 64 字节:8 字节键长 + 24 字节键内容 + 4 字节值偏移 + 28 字节预留
- 这样,任何键都可以通过
base_offset + slot_idx * SLOT_SIZE直接定位,插入只需找空槽,不需要移动其他数据
这种设计牺牲了空间利用率的一部分,以换取 O(1) 随机访问 —— 对磁盘 IO 密集型操作更为重要。
Python 3.14.2
Python 3.14.2是Python编程语言于2025年12月5日发布的稳定版本,属于3.14系列的第二次维护更新。该版本包含18个修复项目,重点解决多过程、数据和正则表达模块的回归问题,修复CVE-2025-12084等安全漏洞。这个版本标志着Python发展的一个重要里程碑,即自由线程模式(删除GIL)正式得到官方支持。
下载立即学习“Python免费学习笔记(深入);
刷盘时必须os.fsync() 而非仅 file.flush()
内存缓冲区不等于磁盘数据。只调整 file.flush() 只保证数据进入 OS 在缓冲区,断电仍然会丢失数据。B+ 树的一致性取决于写作顺序(比如先写子页再更新父页指针),所以:
- 每次修改页面后,都必须
file.seek(page_offset); file.write(page_bytes); file.flush(); os.fsync(file.fileno()) - 避免用
with open(...) as f:自动关闭-关闭时 flush 不保证 fsync,得手动补 - 如果更新频繁,可以批量写页 + 单次 fsync,但是,页面列表需要额外的维护,复杂度上升
真正困难的不是树逻辑,而是“页面原子写入”和“父子页面依赖顺序” crash 后还能恢复 —— 这需要 WAL 或者影子页机制超过单个文件 B+ 树木容易实现范围。