Python中的字典有序无序浅析
一、前言
Python在3.5之前无法保证字典遍历时候与元素添加进入字典时候的顺序一致。而在3.6以后,字典中的元素可以有序遍历,并且相对于3.5也做了空间上的优化。
二、3.5之前
1、初始化字典
初始化空字典的时候,首先会在内存中初始化一个二维数据,数组8行,3列。二维数组中,3列依次存储hash值,键的内存指针,值的指针。比如:
my_dict = {}
# 此时的内存示意图
[[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---]]
2、添加元素
添加元素时候,首先会根据键计算出hash值,然后根据hash值求出存放内存数组中的索引,然后将键的hash值,键值对的地址添加到数组中三列对应位置。比如:
>>> hash('name')
12709084984489
>>> 12709084984489 % 8
5 # 5即为存放在二维数组中的下标为5的位置
Python自带的hash函数也是计算出来的,每次关闭开启Py之后的hash函数不同,但是同一次执行过程中键计算出来的值是相同的。
添加多个元素的时候,如果计算出来的hash值冲突,也会采用开放地址等方式处理该问题。比如:
my_dict['age'] = 26
my_dict['salary'] = 999999
此时的内存数组示意图
[[-4234469173262486640, 指向salary的指针, 指向999999的指针],
[1545085610920597121, 执行age的指针, 指向26的指针],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[1278649844881305901, 指向name的指针, 指向kingname的指针],
[---, ---, ---],
[---, ---, ---]]
可以看到先添加的name,在添加的age和salary,但是在内存数组中的顺序却与添加顺序不一致,因此在遍历的时候也顺序也会无序。
3、内存
对于上述的实现,初始的时候会开辟固定的内存空间,每一行8X3=24字节。当数据超过数组的2/3空间的时候,数组会进行扩容,8行变为16行,变为32行。容量变化的同时,根据hash值计算余数求内存数组对应的下标索引也会出现变化,因此插入新数据的时候如果涉及到扩容,可能需要移动数据,效率较低。
三、3.6之后
1、初始化字典
py3.6之后,初始空字典的时候,底层会有两个数组,一个存放键值对存放内存数组中的索引,一个存放真正的实体。可以看出同样是初始可以存放8个键值对。做了相关的改动和优化。比如
my_dict = {}
此时的内存数组示意图
indices = [None, None, None, None, None, None, None, None]
entries = []
2、插入数据
当添加元素的时候,同样会计算hash值,然后取余,不同的是根据取余的结果作为indices数组的索引修改indices数组相应的位置,修改为实际存放数据的第二个数组entries的索引。比如:
>>> hash('name')
4193068542476671
>>> hash('name') % 8
1 # 修改索引数据indices[1]位置
my_dict['name'] = 'kingname'
此时的内存示意图
indices = [None, 0, None, None, None, None, None, None]
# indices[1] = 0 表示真实数据是存放在entries[0]的位置
entries = [
[-5954193068542476671, 指向name的指针, 执行kingname的指针]
]
当插入多个元素时候,entries二维数组按照顺序存储插入的元素。
my_dict['address'] = 'xxx'
my_dict['salary'] = 999999
此时的内存示意图
indices = [1, 0, None, None, None, None, 2, None]
entries = [
[-5954193068542476671, 指向name的指针, 执行kingname的指针],
[9043074951938101872, 指向address的指针,指向xxx的指针],
[7324055671294268046, 指向salary的指针, 指向999999的指针]
]
读取数据时候,根据hash函数计算出hash值,然后求出indices的下标x,根据indices[x]的值就可以读取到真正的键值对,遍历的时候也可以做到 有序遍历。
3、占用内存
相比较于3.5之前的版本,空间也做了优化,初始化的时候不需要再固定开辟一个二维数组,只有一个固定长度的indices数组。
posted on 2022-02-21 20:56 weilanhanf 阅读(962) 评论(0) 编辑 收藏 举报