其实 在你看了笔者的文章之前,或许想过这样一个问题,
为什么列表是有顺序的呢?而字典不是?
来看一下在内存中是怎样存储的就知道了:
列表的存储是顺序存储,就像你上大学老师会点名的花名册一样,写的时候按顺序写,读的时候也是按顺序读的.
而字典,在Python3.5之前,不能说无序,只能说不确定,时而有序是而无序.存储结构是hash表,是通过索引查找数据,所以存储的位置很随机,而读取是顺序读取得,因此写入和读取得顺序不同。他的旧结构就是这样的了
--+-------------------------------+ | 哈希值 (hash) 键 (key) 值 (value) --+-------------------------------+ 0 | hash0 key0 value0 --+-------------------------------+ 1 | hash1 key1 value1 --+-------------------------------+ 2 | hash2 key2 value2 --+-------------------------------+ . | ... __+_______________________________+
那我们再来看一下结构
Indices ---------------------------------------------------- None | index0 | None | None | index2 | None | index1 ... ---------------------------------------------------- Entries -------------------- hash0 key0 value0 --------------------- hash1 key1 value1 --------------------- hash2 key2 value2 --------------------- ... ---------------------
新结构由 Indices(索引,数组实现) 和 Entries(实体,PyDictObject类型) 两种结构组成。
- 当插入一个数据时,先计算数据对应的hash值并映射成 Indices 数组的一个下标,没有冲突的话就将另一个值 Entries_index(暂时这么叫吧) 填入Indices数组中下标对应的位置。并在Entries后面追加一行记录,类似
hash值, key值, value值
。如果冲突的话可以用基本的解决冲突的办法,这里不赘述了。
这种方法,字典 增删改查的时间复杂度 会有以前的O(1) 变为O(2),因为多了一步查找的过程。而且字典扩容和缩容时要按照Indices的顺序来保持字典始终有序。
但是至少有两个优化。
- 字典占用的内存变小了。旧的字典总会预留大于 1/3的容量的hash位置,防止hash碰撞过多影响效率。现在则不必预留。
- 字典有序了。
https://github.com/python/cpython/blob/3d75bd15ac/Include/dictobject.h#L23