为什么python3.6以后的字典输出顺序和储存时的顺序相同?

在python3.5及其以前,初始化一个字典的时候cpython底层会初始化一个二维数组,该数组的shape是(8, 3):
在这里插入图片描述

假设该字典的名称为my_dict,当向字典里插入一个键值对的时候:

my_dict['studentID'] = 12345

然后输出当前运行的哈希值(注意每一个运行时的时候哈希值不变,但是关了再次打开会改变):

h = hash('studentID')
print(h)  # 7351990786725726507
print(h % 8)  # 3

对8取余之后的余数为3,那么这个键值对储存在二维数组下标为3的那一行中,那一行中分别储存着hash(‘studentID’),指向’studentID’的指针,指向12345的指针,如图:
在这里插入图片描述
当你要循环遍历字典的Key的时候,Python底层会遍历这个二维数组,如果当前行有数据,那么就返回Key指针对应的内存里面的值。如果当前行没有数据,那么就跳过。所以总是会遍历整个二位数组的每一行。

每一行有三列,每一列占用8byte的内存空间,所以每一行会占用24byte的内存空间。

由于Hash值取余数以后,余数可大可小,所以字典的Key并不是按照插入的顺序存放的。

当两个不同的Key,经过Hash以后,再对8取余数,可能余数会相同。此时Python为了不覆盖之前已有的值,就会使用 开放寻址技术重新寻找一个新的位置存放这个新的键值对。

当字典的键值对数量超过当前数组长度的2/3时,数组会进行扩容,8行变成16行,16行变成32行。长度变了以后,原来的余数位置也会发生变化,此时就需要移动原来位置的数据,导致插入效率变低。python这种机制在python很多地方都可以见到,比如python列表容器。

在Python 3.6以后,字典的底层数据结构发生了变化,现在当你初始化一个空的字典以后,它在底层是这样的:

L = [None, None, None, None, None, None, None, None]

array = []

当你初始化一个字典以后,Python单独生成了一个长度为8的一维数组L。然后又生成了一个空的二维数组array。
如果往字典里添加一个键值对:

my_dict = {}
my_dict['a'] = 1
print(hash('a'))  # 7351990786725726507
print(hash('a') % 8)  # 3

python底层的实际结构是这样的:

L = [None, None, None, 0, None, None, None, None]
array = [[7351990786725726507, 'a'的指针, 1的指针]]

再添加一个键值对:

my_dict['b'] = 2
print(hash('b'))  # -8440756067545395779
print(hash('b') % 8)  # 5

python的底层结构实际上是这样的:

L = [None, None, None, 0, None, 1, None, None]
array = [[7351990786725726507, 'a'的指针, 1的指针]
		 [-8440756067545395779, 'b'的指针, 2的指针]]
		 

那么现在读取值的时候怎么办呢?比如读取’b’的值,首先计算’b’的哈希值hash(‘b’), 然后计算哈希值除以8的余数值,得到5,就去读取L的第五个元素,值为1,最后定位到二维数组的第1行(指的是下标,即还有第0行),再输出相应的值。
这样一来,每一次添加的键值对可以按照顺序添加在二维数组的后面,输出字典的时候直接遍历二维数组即可,输出顺序的就是添加的顺序。二维数组中的每一行都有数据,这样就避免跳过,减少了遍历的时间。

3.5版本以及之前版本,即使有效数据只有3行,但它占用的内存空间还是 8 * 24 = 192 byte(初始创建的就是8行)。但是3.5版本之后,如果只有三行有效数据,那么 二维数组也就只有3行,占用的空间为3 * 24 =72 byte,而L只是一个一维的数组,只占用8 byte,所以一共占用 80 byte。大大减少了内存空间的浪费。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值