1. 程式人生 > 程式設計 >Python字典底層實現原理詳解

Python字典底層實現原理詳解

在Python中,字典是通過散列表或說雜湊表實現的。字典也被稱為關聯陣列,還稱為雜湊陣列等。也就是說,字典也是一個數組,但陣列的索引是鍵經過雜湊函式處理後得到的雜湊值。雜湊函式的目的是使鍵均勻地分佈在陣列中,並且可以在記憶體中以O(1)的時間複雜度進行定址,從而實現快速查詢和修改。雜湊表中雜湊函式的設計困難在於將資料均勻分佈在雜湊表中,從而儘量減少雜湊碰撞和衝突。由於不同的鍵可能具有相同的雜湊值,即可能出現衝突,高階的雜湊函式能夠使衝突數目最小化。Python中並不包含這樣高階的雜湊函式,幾個重要(用於處理字串和整數)的雜湊函式是常見的幾個型別。

通常情況下建立雜湊表的具體過程如下:

資料新增:

把key通過雜湊函式轉換成一個整型數字,然後就將該數字對陣列長度進行取餘,取餘結果就當作陣列的下標,將value儲存在以該數字為下標的陣列空間裡。

資料查詢:再次使用雜湊函式將key轉換為對應的陣列下標,並定位到陣列的位置獲取value。

雜湊函式就是一個對映,因此雜湊函式的設定很靈活,只要使得任何關鍵字由此所得的雜湊函式值都落在表長允許的範圍之內即可。本質上看雜湊函式不可能做成一個一對一的對映關係,其本質是一個多對一的對映,這也就引出了下面一個概念–雜湊衝突或者說雜湊碰撞。雜湊碰撞是不可避免的,但是一個好的雜湊函式的設計需要儘量避免雜湊碰撞。

Python2中使用使用開放地址法解決衝突。

CPython使用偽隨機探測(pseudo-random probing)的散列表(hash table)作為字典的底層資料結構。由於這個實現細節,只有可雜湊的物件才能作為字典的鍵。字典的三個基本操作(新增元素,獲取元素和刪除元素)的平均事件複雜度為O(1)。

Python中所有不可變的內建型別都是可雜湊的。

可變型別(如列表,字典和集合)就是不可雜湊的,因此不能作為字典的鍵。

常見的雜湊碰撞解決方法:

1 開放定址法(open addressing)

開放定址法中,所有的元素都存放在散列表裡,當產生雜湊衝突時,通過一個探測函式計算出下一個候選位置,如果下一個獲選位置還是有衝突,那麼不斷通過探測函式往下找,直到找個一個空槽來存放待插入元素。

開放地址的意思是除了雜湊函式得出的地址可用,當出現衝突的時候其他的地址也一樣可用,常見的開放地址思想的方法有線性探測再雜湊,二次探測再雜湊等,這些方法都是在第一選擇被佔用的情況下的解決方法。

2 再雜湊法

這個方法是按順序規定多個雜湊函式,每次查詢的時候按順序呼叫雜湊函式,呼叫到第一個為空的時候返回不存在,呼叫到此鍵的時候返回其值。

3 鏈地址法

將所有關鍵字雜湊值相同的記錄都存在同一線性連結串列中,這樣不需要佔用其他的雜湊地址,相同的雜湊值在一條連結串列上,按順序遍歷就可以找到。

4 公共溢位區

其基本思想是:所有關鍵字和基本表中關鍵字為相同雜湊值的記錄,不管他們由雜湊函式得到的雜湊地址是什麼,一旦發生衝突,都填入溢位表。

5 裝填因子α

一般情況下,處理衝突方法相同的雜湊表,其平均查詢長度依賴於雜湊表的裝填因子。雜湊表的裝填因子定義為表中填入的記錄數和雜湊表長度的比值,也就是標誌著雜湊表的裝滿程度。直觀看來,α越小,發生衝突的可能性就越小,反之越大。一般0.75比較合適,涉及數學推導。

在python中一個key-value是一個entry,

entry有三種狀態。

Unused: me_key == me_value == NULL

Unused是entry的初始狀態,key和value都為NULL。插入元素時,Unused狀態轉換成Active狀態。這是me_key為NULL的唯一情況。

Active: me_key != NULL and me_key != dummy 且 me_value != NULL

插入元素後,entry就成了Active狀態,這是me_value唯一不為NULL的情況,刪除元素時Active狀態刻轉換成Dummy狀態。

Dummy: me_key == dummy 且 me_value == NULL

此處的dummy物件實際上一個PyStringObject物件,僅作為指示標誌。Dummy狀態的元素可以在插入元素的時候將它變成Active狀態,但它不可能再變成Unused狀態。

為什麼entry有Dummy狀態呢?這是因為採用開放定址法中,遇到雜湊衝突時會找到下一個合適的位置,例如某元素經過雜湊計算應該插入到A處,但是此時A處有元素的,通過探測函式計算得到下一個位置B,仍然有元素,直到找到位置C為止,此時ABC構成了探測鏈,查詢元素時如果hash值相同,那麼也是順著這條探測鏈不斷往後找,當刪除探測鏈中的某個元素時,比如B,如果直接把B從雜湊表中移除,即變成Unused狀態,那麼C就不可能再找到了,因為AC之間出現了斷裂的現象,正是如此才出現了第三種狀態---Dummy,Dummy是一種類似的偽刪除方式,保證探測鏈的連續性。

提示

一般情況下普通的順序表陣列儲存結構也可以認為是簡單的雜湊表,雖然沒有采用雜湊函式(取餘),但同樣可以在O(1)時間內進行查詢和修改。但是這種方法存在兩個問題:擴充套件性不強;浪費空間。

set集合和dict一樣也是基於散列表的,只是他的表元只包含鍵的引用,而沒有對值的引用,其他的和dict基本上是一致的,所以在此就不再多說了。並且dict要求鍵必須是能被雜湊的不可變物件,因此普通的set無法作為dict的鍵,必須選擇被“凍結”的不可變集合類:frozenset。顧名思義,一旦初始化,集合內資料不可修改。

以上這篇Python字典底層實現原理詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。