字典核心底层原理
字典对象的核心是散列表,散列表是一个稀疏数组(总有空白元素的数组)数组的
每个单元叫做bucket。每个bucket由两部分:一个是键对象的引用,一个是值对象的引用
由于,所有bucket结构和大小一致,我们可以通过偏移量来读取指定bucket
字典元素的访问也可以通过索引来访问,也就是字典元素包括三个方面,索引,键对象,值对
象,它通过索引来确定键对象的位置
假设我们的字典创建完成之后,数组长度是8,首先我们列出0-7的二进制编码,前面补0成为
三位数
0 0 变成 0 000 前面补0凑生三位
1 1 1 001
2 10 2 010
3 11 3 011
4 100 4 100
5 101 5 101
6 110 6 110
7 111 7 111
我们创建一个数组,然后计算键“name”的散列值,可以通过hash()来计算,bin()是
将里面的数值转化为二进制编码
>>> a = {"name":"www","age":18}
>>> hash("name")
-2129792525715549463
>>> bin(hash("name"))
'-0b1110110001110100010110000010010011100100011101010110100010111'
像我们取得字典长度为8,它的填入方式是计算处散列值的最右边的三位数值的偏移量
及(111),十进制是3,我们查看偏移量为3对应的bucket是否为空,如果为空,则将键值
放进去,如果不为空则依次取右边三位作为偏移量,及010,十进制是2,查看偏移量是否为
空,直到找到我胃口的bucket将键值放进去,如果八个位置都满了,他会自动扩容到十六进制,一般满三分之二后就会自动扩容
8 1000 9 1001 10 1010 11 1011
12 1100 13 1101 14 1110 15 1111
关于进制问题的个人看法,我们发现为什么八进制,十六进制,三十二进制这些进制比较常用
也是有些原因的,像8进制0-7,计算机一般从0开始计数,7的二进制是111 15的二进制是
1111,如果是十进制,我们发现它正好卡在三位和四位二进制之间,如果我们安四个二进制
一读取,那么出现大于10小于15的数值如何判断,因为他们也是四位二进制,也就是说计算
机想要进行十进制转换,在大于7,小于10这部分还要加一个判断语句,这也就是为什么计算
机常用二进制,八进制,十六进制的原因了,计算机是二进制的,转化为八进制读取二进制前
三位数,十六进制读取前四位数,这种转化是非常简单快速的,如果转化为其他进制还需要运
行某些判断语句
根据键查找“键值对”的底层过程
我们我们访问字典元素的时候就是通过“键对象”查找到“键值对”,从而找到值对象
根据键对象查找的方法和填入的方法差不多,都是要计算“键对象”的散列值,依次取不同二
十的二进制散列值,假设长度为8,我们就拿出计算的散列值的最右边的3位二进制数字作为
偏移量,这中间会有一个判断,因为我们是按三位数字的取值方法,所以会有重复的可能,它
在找到对应的偏移量之后会有一个整体判断的环节,如果整体不同,则查找下三位的二进制数
如果“值对象”为空,则返回None。如果取完所有三列值仍然没有找到,那么也会返回
None
用法总结:
键必须可散列
数字,字符串,元组都是可散列的
自定义对象需要支持下面三点
支持hash()函数
支持通过__eq__()方法检查相同性
若a ==b为真,则hash(a)==hash(b)也为真