Python的Dict对象源码分析
作者:efeics
Python的Dict对象源码分析
PyDictObject即字典对象,类似于C++ STL中的map,但STL中以红黑树实现,Python中dict以hash表(散列表)实现。
散列表,通过Hash函数将特定对象映射为特定数字;
当装载率大于2/3时,散列冲突概率增加,解决散列冲突,STL采用开链法,而Python采用开放定址法。
开放定址法法,在探测冲突链上依次跳转,如果删除探测冲突链上某个元素,会使探测冲突链断裂。
故而,删除某元素时,不可在物理上真正删除。
1、entry与dict对象
typedef struct { Py_ssize_t me_hash; // key的hash值 PyObject *me_key; PyObject *me_value; } PyDictEntry;
struct _dictobject { PyObject_HEAD Py_ssize_t ma_fill; /* # Active + # Dummy */ Py_ssize_t ma_used; /* # Active */ Py_ssize_t ma_mask; // dict对象所拥有的entry数量 PyDictEntry *ma_table; // 指向存放元素的内存区域 PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash); // 搜索函数指针 PyDictEntry ma_smalltable[PyDict_MINSIZE]; // 自带少量元素内存 };
元素数量不同时,可使用不同策略
2、搜索策略
由于采用开放定址法,探测冲突链上元素不可删除,Python将每个元素设为三种状态。
- 从未使用是unused
- 正在使用是Active
- 当将某个Active的元素删除时将其置为dummy。
通过ma_key与ma_value的值来判定状态。
Python每种类型的对象都实现了各自的hash函数,如string对象函数函数如下:
int len = strObject->length; unsignedchar * p = (unsignedchar *)strObject->value; long x = *p << 7; while (--len >= 0) x = (1000003*x) ^ *p++; x ^= strObject->length; if (x == -1) x = -2; strObject->hashValue = x;
产生散列冲突时,采用二次探测函数,Python中如下:
for (perturb = hash; ; perturb >>= 5) { i = (i << 2) + i + perturb + 1; ep = &ep0[i & mask]; }
冲突探测链开端为通过hash函数找到的第一个entry,结尾为第一个unused状态entry。
搜索整体思想为,在探测链上依次寻找;
遇到dummy状态则记录为freesplot,并继续探测;
直至探测到匹配的元素,返回该对象;
或者直到最后一个元素即unused状态entry,此时若freesplot为空则返回该unused,若不为空则返回freesplot记录的dummy。
有图示几种情况,1-6依次返回A、C、D、E、X、Y。
3、dict对象使用
dict对象的各种应用均依赖与搜索策略,而在python中使用PyStringObject*做key的情况特别多,python默认使用针对string优化的搜索函数,如果key不是string对象,返回通用搜索函数。
元素的插入,需注意dict对象会自动调整内存大小,以装载率大于2/3为基准。
调整大小时会重新分布元素,故dummy态不再需要,被销毁。 元素的删除,需注意该元素置为dummuy态。
元素的设置,若存在该key,直接更改value;
若不存在,添加该entry,即(key,value)。
4、dict对象缓冲池
和list对象缓冲池类似,在此就不多赘述。
到此这篇关于Python的Dict对象源码分析的文章就介绍到这了,更多相关Python的Dict源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!