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數組。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM