【python】python中字典的底层是怎么实现的
一、相关概念
python字典的底层实现的是哈希表。调用python内置的哈希函数,将键(key)作为参数进行转换(哈希运算+取余运算),得到一个唯一的地址(地址的索引),然后将值(value)存放到对应的地址中(给相同的键赋值会直接覆盖原值,因为相同的键转换后的地址时一样的)
哈希表(Hash Table,又称为散列表)是一种线性表的存储结构。由一个直接寻址表 T (假设大小为m) 和一个哈希函数 h(k) 组成。对于任意可哈希对象,通过哈希函数(一般先进行哈希计算,然后对结果进行取余运算),将该对象映射为寻址表的索引 [0,1,2,…m-1],然后在该索引所对应的空间 T[0,1,2,…,m-1] 进行变量的存储/读取等操作。如下图所示。
二、哈希表
用顺序表存储数据
存储键值对时,通过哈希函数计算出键对应的索引,将值存到索引对应的数据区中
获取数据时,通过哈希函数计算出键对应的索引,将该索引对应的数据取出来
注:键(Key)必须是可哈希的,即,通过哈希函数可为此键计算出唯一地址。
对于 Python 来说,变量,列表、字典、集合这些都是可变的,所以都不能做为键(Key)来使用。因为元组里边可以存放列表这类可变元素,所以如果实在想拿元组当字典的键(Key),那必须对元组做限制:元组中只包括像数字和字符串这样的不可变元素时,才可以作为字典中有效的键(Key)。另外还需要注意的一点是,Python 的哈希算法对相同的值计算得到的结果是一样的,也就是说 12315 和 12315.0 的值相同,他们被认为是相同的键(Key)。
三、Python中字典的操作
插入: 对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的表地址空间为空,将键值对存入该地址空间;
查询/更新: 对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要查询/更新的健一致,那么就将该键值对取出来 / 更新该键所对应的值;
扩容: 字典初始化的时候,会对应初始化一个有k个空间的表,等空间不够用的时候,系统就会自动扩容,这时候会对已经存在的键值对 重新进行哈希取余运算(重新进行插入操作)保存到其它位置;
四、哈希冲突
对于任何哈希函数,都会出现两个不同的元素映射到同一个位置上的情况,这种情况称为哈希冲突
五、解决哈希冲突——开链法
哈希表的每一个位置都连接一个链表,当发生冲突时,冲突的元素会被加到该位置的链表的最后
六、解决哈希冲突——开放寻址法
如果哈希函数得到的位置i已经有数据了,那么就往后探查新的位置来存储这个值
线性探测:如果i有数据了,则探测i+1,i+2…以此类推,直到找到空的位置
1、存储值的过程
a、key=apple,cat,dog,hello,通过哈希函数映射之后得到的值都为2
b、现有一个字典dic={‘apple’:1,‘cat’:2,‘dog’:3},存储数据,
c、原索引为2的位置没有存储数据,此时将apple的值1存储到这个
d、接着cat寻址到索引为2的位置,发现这个位置已经有值了,会继续往后探测,依次类推
2、获取值的过程
a、获取dic[‘dog’]的时候,先到索引为2的位置去获取
b、获取不到继续向后探测
3、删除值得过程
dic.remove(‘cat’)
a、给每一个节点定义一个状态
未使用
已使用
已删除
七、开链法和开放寻址法的区别
开链法:
优点:删除节点比较容易,数据量比较大使用开链法
缺点:使用空间比较大
开放寻址法:
删除节点不能真正的把节点删掉,给每一个节点定义一个状态,数据量比较小使用开放寻址法
缺点:
a、使用开放寻址法,那么顺序表总归会有一天会填满
b、一般为了保证插入和查找的效率,哈希表一般在元素数量在容量的2/3时,就会进行扩容
c、扩容之后,计算的哈希函数也会随之变化,那么里面的数据存储的顺序也会变化