Python字典物件實現原理

2021-09-02 13:18:00 字數 3845 閱讀 3809

字典型別是python中最常用的資料型別之一,它是乙個鍵值對的集合,字典通過鍵來索引,關聯到相對的值,理論上它的查詢複雜度是 o(1) :

>>> d = 

>>> d['c'] = 3

>>> d

在字串的實現原理文章中,曾經出現過字典物件用於intern操作,那麼字典的內部結構是怎樣的呢?pydictobject物件就是dict的內部實現。

雜湊表 (hash tables)

雜湊表(也叫雜湊表),根據關鍵值對(key-value)而直接進行訪問的資料結構。它通過把key和value對映到表中乙個位置來訪問記錄,這種查詢速度非常快,更新也快。而這個對映函式叫做雜湊函式,存放值的陣列叫做雜湊表。 雜湊函式的實現方式決定了雜湊表的搜尋效率。具體操作過程是:

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

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

但是,對key進行hash的時候,不同的key可能hash出來的結果是一樣的,尤其是資料量增多的時候,這個問題叫做雜湊衝突。如果解決這種衝突情況呢?通常的做法有兩種,一種是鏈結法,另一種是開放定址法,python選擇後者。

開放定址法(open addressing)

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

pydictentry

字典中的乙個key-value鍵值對元素稱為entry(也叫做slots),對應到python內部是pydictentry,pydictobject就是pydictentry的集合。pydictentry的定義是:

typedef struct  pydictentry;
me_hash用於快取me_key的雜湊值,防止每次查詢時都要計算雜湊值,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是一種類似的偽刪除方式,保證探測鏈的連續性。

pydictobject

pydictobject就是pydictentry物件的集合,pydictobject的結構是:

typedef struct _dictobject pydictobject;

struct _dictobject ;

pydictobject使用pyobject_head而不是pyobject_var_head,雖然字典也是變長物件,但此處並不是通過ob_size來儲存字典中元素的長度,而是通過ma_used欄位。

pydictobject的建立過程

pyobject *

pydict_new(void)

if (numfree) else

assert (mp->ma_used == 0);

assert (mp->ma_table == mp->ma_smalltable);

assert (mp->ma_mask == pydict_minsize - 1);

} else

mp->ma_lookup = lookdict_string;

return (pyobject *)mp;

}

初始化dummy物件

如果緩衝池還有可用的物件,則從緩衝池中讀取,否則,執行步驟3

分配記憶體空間,建立pydictobject物件,初始化物件

指定新增字典元素時的探測函式,元素的搜尋策略

字典搜尋策略

static pydictentry *

lookdict(pydictobject *mp, pyobject *key, register long hash)

else

}freeslot = null;

}/* in the loop, me_key == dummy is by far (factor of 100s) the

least likely outcome, so test for that last. */

for (perturb = hash; ; perturb >>= perturb_shift)

else

}else if (ep->me_key == dummy && freeslot == null)

freeslot = ep;

}assert(0); /* not reached */

return 0;

}

字典在新增元素和查詢元素時,都需要用到字典的搜尋策略,搜尋時,如果不存在該key,那麼返回unused狀態的entry,如果存在該key,但是key是乙個dummy物件,那麼返回dummy狀態的entry,其他情況就表示存在active狀態的entry,那麼對於字典的插入操作,針對不同的情況進行操作也不一樣。對於active的entry,直接替換me_value值即可;對於unused或dummy的entry,需要同時設定me_key,me_hash和me_value

pydictobject物件緩衝池

pydictobject物件緩衝池和pylistobject物件緩衝池的原理是類似的,都是在物件被銷毀的時候把該物件新增到緩衝池中去,而且值保留pydictobject物件本身,如果ma_table維護的時從系統堆中申請的空間,那麼python會釋放這塊記憶體,如果ma_table維護的是ma_smalltable,那麼只需把smalltable中的元素的引用計數減少即可。

static void

dict_dealloc(register pydictobject *mp)

}if (mp->ma_table != mp->ma_smalltable)

pymem_del(mp->ma_table);

if (numfree < pydict_maxfreelist && py_type(mp) == &pydict_type)

free_list[numfree++] = mp;

else

py_type(mp)->tp_free((pyobject *)mp);

py_trashcan_safe_end(mp)

}

Python字典物件實現原理

字典型別是python中最常用的資料型別之一,它是乙個鍵值對的集合,字典通過鍵來索引,關聯到相對的值,理論上它的查詢複雜度是 o 1 d d c 3 d 在字串的實現原理文章中,曾經出現過字典物件用於intern操作,那麼字典的內部結構是怎樣的呢?pydictobject物件就是dict的內部實現。...

Python 字典實現原理

a a key1 1 a key2 6 del a key1 python直譯器 執行 a python直譯器讀到這裡,比如會給5個連續的記憶體空間,有5個連續的記憶體位址,可以放資料 python直譯器 執行 a key1 1 這裡,python直譯器會對key1進行雜湊運算,得到乙個十位進製的雜...

Python字典的實現原理

python中的字典底層依靠雜湊表 hash table 實現,使用開放定址法解決衝突,雜湊表是key value型別的資料結構,可以理解為乙個鍵值需要按照一定規則存放的陣列,而雜湊函式就是這個規則 字典本質上是乙個雜湊表 總有空白元素的陣列,python至少保證1 3的陣列是空的 字典中的每個鍵都...