Python (cpython) 的 dict 是用哈希表实现的。
碰撞的处理方法是 open addressing,因为 chaining 的 overhead 太高 (哈希表里记录的只是哈希值和相关的 object 的地址,不记录实际 object,所以如果用链表来做 chaining 的话,光 next 指针就占去了很多空间)。(不知道我理解得对不对)。
Open addressing is preferred over chaining since the link overhead for
chaining would be substantial (100% with typical malloc overhead).
- 一般的哈希表在选择哈希函数时,都希望产生的哈希值尽量随机,但是 python 的哈希函数不是这样的。dict 和其它一些地方使用的哈希函数其实就是 python 的内置函数
hash,它的返回值非常的不随机。比如对于最重要的两种 key,整数和字符串,整数的哈希值就是它本身,而字符串的哈希值计算也很简单。且看
1 2 3 4 | |
这样的好处是如果 key 是连续的整数或者字符串,几乎不会有碰撞。而且哈希值基本都是连续的,效率也比较高。
有时候,好处也是坏处。这样做的坏处是,表里会有数据连续地存在一块,而不是相互散开 (这正是哈希表,也称散列表的意义所在啊),也就是,一旦有碰撞发生,用线性探测的方法来寻找可用位置是绝对不可行的。python 用的探测方法是一种 quadratic probing,保证不会受到连成块的数据的影响。
既然是使用 open addressing,也就是说整个哈希表存储在一段连续的内存中,或者说是一个数组 (dict 是用 C 实现的)。这意味着,当数组快满时 (比如超过 2/3),需要把数组扩大 (resize)。
python 保证在修改元素的时候不会发生 resize,也就是保证了可以一边遍历 dict,一边修改元素。但是其它的操作就不一定了,所以会有这样的错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
参考
http://www.laurentluce.com/posts/python-dictionary-implementation/
http://hg.python.org/cpython/file/tip/Objects/dictobject.c
http://en.wikipedia.org/wiki/Hash_table#Collision_resolution