Dictionary通过名字你就知道它是一个字典,字典的话那就是有一个关键字和值,我们通过关键字来查找对应的值。
Dictionary的内部存储机制:hash表
我们Python中的Dictionary是基于hash表,hash表的存储方式跟树有什么区别呢?首先它的存储空间还是连续的,也就是说它的读取速度会更快,因为如果用树来存储的话,它的读取速度是O(logn),而如果我们用hash表来存储的话,它的读取速度是O(1),也就是说只要经过单次操作就可以直接访问到这个值,它是怎么做到的呢?就是把每一个key都算出hash值,算出来之后我们再以hash值来插入进来,比如上图这个c我们给它算出的hash值是3,那我们就把这个c插入到索引3的位置上来。
这个时候这个速度就很快了,我们下次在查找c的时候怎么做呢?先把c转化成hash值,就得到这个元素的下标3了,我们直接通过下标3一下子就找到这个数据了,所以它的时间复杂度就变成了O(1),是快速的查找。
但是这样会出现一个问题,对于不同的关键字hash值的计算有可能会一样,不一样的可能性也很大,问题是实际上我们算出的hash值是很大的,比如算出了一个几十万大小的值,那我们能不能拿这个值直接算出下标呢,那你得准备多大的数组啊!所以你肯定要转换,在Python的源码当中它的转换方式很简单,就是做一次与操作:
比方说我这个数组的大小是9,那我就拿c跟9与一下,其实就把这个值的高位全去掉了,那肯定就小于9了,那我们就算出了它的位置3,比方说是1万03的,算出来之后等于3,由于它做了hash的这种与操作之后,那使得你的hash冲突的概率大大的加大了,本来两个不同的字符串生成的同样hash值的概率是很小的,但现在这样做概率就非常大,那我们如何来解决这个冲突呢?
假定我们的c和z生成了同样的hash值3,那我们怎么取访问呢?有多种方法,比方说大家都是3,那我在3下面创建一个链表,但这种方案使得我们的数据不连续了,因为变成链表了,我们其实相当于是一个线性表来存储的。
我们Python采用的方式是开放地址的方法,或者也称之为再散列的算法,它其实就是说,假定我们第一个c插在3这个位置,但是我这个z也是3的时候,因为发生冲突了,它会再做一次散列,它有一个二次探查的函数,二次探查的函数应该是会跳到相应的两倍的位置上去继续查找,就像2分查找一样,从两边开始继续查找,就是使得你第2次再冲突的概率降低,它通过二次探查法假设找到了,把z存在了6这个位置,这样的话下次你在找z这个值的时候,也是做一次散列,再做二次探查,找到对应的key就是6。
这样我们知道了Dictionary的存储方式内部是hash表,它会解决这种hash冲突的问题,你要考虑内部性能的时候都要考虑Python的内部机制。