今天继续从第五章开始学习.
这是 python 提供的关联容器, 即 key,value 对映射, python 使用 hash table 的方式实现此关联容器.
(也有别的实现方式, 如 STL 中使用 RB-tree 红黑树等).
由于不同的(动态)语言实现此类结构的策略有不同, 我们可以仔细看看 python 的实现有什么特点.
书中关于 hash-table 的基本知识略. (大概 python 使用开放定址法解决冲突?)
定义: 将关联容器中的一个 <键,值> 对 叫做 entry, 或 slot. 在 python 中该结构为:
// 定义于 dictobject.h 文件中. struct PyDictEntry { size_t me_hash; // 缓存 key 的哈希值. PyObject *me_key; // key,value 对 PyObject *me_value; }
此结构中提供 me_hash 用空间换时间, 缓存 key 的 hash 值.
根据 me_key, me_value 的值有三种状态组合:
1. key==null, value==null, 这是一个空的 entry, 可存放新值.
2. key!=null, value!=null, 这是一个 active 状态的 entry, 即有 key-value 对.
2. key=dummy(特殊键值) 这是一个 Dummy 态(墓碑). 为保证冲突检测链有效.
理论见算法书各种 hash 实现技术. 在 python 中实现结构如下:
class PyDictObject { HEAD 部分; size_t ma_fill; // 元素个数. active+dummy size_t ma_used; // 元素个数. active size_t ma_mask; PyDictEntry *ma_table; // 指向 entry 数组 PyDictEntry *(*ma_lookup)(...); PyDictEntry ma_smalltable[MINSIZE=8]; }
在尾部有一个 8 个entry 的小数组, 数字 8 是大量实验得出的最佳值. 它取得某种好的平衡.
如果 dict 中元素数量少的时候, ma_table 指向内部的 ma_smalltable; 否则指向额外分配的内存区域.
方法 PyDict_New() 用于创建一个新 dict 对象. (我们可将其看做是构造函数)
同样内部有一个 dict 对象的缓冲池, 如同 list 对象的.
搜索函数 ma_lookup() 被初始化为 lookdict_string, 它实现 dict 对象的搜索策略.
Python 提供两种搜索策略, lookdict 和 lookdict_string, 由于 string 常用, 故而选择为默认的.
搜索算法(略微伪代码一点):
dictentry *lookdict(*dict, *key, hash) { // 1. 根据 hash, dict 大小(mask) 计算所在 entry 位置. int idx = hash & dict.ma_mask; // 这里 mask 形式为 2^N-1, 故而可以用 & 计算. // 这里 entries = dict.ma_table; dictentry *entry = &entries[idx]; // 2. 如果 idx 对应 entry 就是 key, 则找到返回. if (entry.key == (null || key)) return entry; // 找到了. // 3. 如果是 dummy 的, 则算是找到了一个 free-slot (可用位置), 用于插入新值. if (entry.key == dummy) free_slot = entry; else { if (entry.hash == hash && entry.key == key) ... } // 4. 上面检测到了冲突. 今计算下一个探测位置, 检查是否可用... // 探测地址函数似乎是 next_i = i*5 + 1 + 某个递减的因子; // 注释中说参见 Knuth 的算法书, base on Algorithm D from Knuth Vol.3, Sec. 6.4. }
源码中有一个 dictnotes.txt 文件, 似乎描述 dict 对象的优化思路等.
伪代码中 entry.key == key 是一种简化写法, 实际 python 对于什么是 == (相同) 是表示值相同.
为了比较值相同, 用简单的 `==' 是远远不够的, 实际代码中使用 PyObject_RichCompareBool() 来完成.
此函数实际承担两种任务, 分别是 1.查找(找到指定key对应的entry,或未找到); 2.插入(找到key 的插入位置entry,
如果有则返回覆盖, 没有则返回可用位置).
这样, 向 hash 中插入 key,value 对时, 就是先用 look 查找, 结果可能是 1.找到, 则覆盖; 2.未找到, 则新添加 entry.
同样, 删除操作也是先 look, 找到则删除, 未找到则返回 失败即可.
由于这是一个典型的 hash 实现, 理解理论的最好办法是看算法书, 所以细节的就不研究那么多了.
类似于 list 对象, dictobject.c 内部使用一个 free_dicts[80] 数组维护一个简单的缓冲池.
===============================================================
用前面几章介绍的对象, 做出一个简单的 python, 这个模拟作者称之为 small python.
看书, 作者大量简化原 python 对象代码, 以实现一个非常简单的 python. 或者叫简陋的 python.
略去.
下面作者准备带我们去探索: 字节码, 虚拟机, 条件判断, 函数, 类, 异常等 第三部分, 所以我们也换一篇学习笔记...