1. 程式人生 > >儲存 dict 的元素前是計算 key 的 hash 值?

儲存 dict 的元素前是計算 key 的 hash 值?

dict 的高效能與其儲存方式是分不開的,我們知道 dict 的儲存是基於雜湊表(又稱散列表),需要計算 hash 值,那麼是計算誰的 hash 值呢?是像別人說的:儲存 dict 元素前計算 key 的 hash 值?

驗證

這裡先建立個字典

>>> my_dict = {'a': 'apple', 'b': 'banana'}

由於雜湊表是一塊連續的記憶體空間(陣列),在不考慮 hash 值衝突的情況下,如果計算的是 key 的 hash 值,那麼:'a' 的 hash 值與 'b' 的 hash 值之間的差值'a' 的記憶體地址與 'b' 的記憶體地址之間的差值(可理解為記憶體地址裡的距離)

相等才對,也就是說以下的等式成立才對

hash('a') - hash('b') == id('a') - id('b')

但事實上面等式返回的是 False

>>> hash('a') - hash('b') == id('a') - id('b')
False

先看看其中各項的具體值是多少

>>> hash('a')
-7336862871683211644
>>> hash('b')
3607308758832868774
>>> id('a')
1290454097736
>>> id('b')
1290454096056
>>> id('a') - id('b')
1680
>>> hash('a') - hash('b')
-10944171630516080418

可以很明顯得看到差距還是挺大的這說明計算的不是 key 的 hash 值(這種說法不夠嚴謹),那計算的是什麼呢?

計算的是 key 所在記憶體地址的 hash 值

在不考慮 hash 衝突的情況下, 'a' 所在記憶體地址的 hash 值與 'b' 所在記憶體地址的 hash 值之間的差值'a' 的記憶體地址與 'b' 的記憶體地址之間的差值 相等,也就是說以下的等式成立才對

hash(id('a')) - hash(id('b')) == hash(id('a')) - hash(id('b'))
>>> hash(id('a')) - hash(id('b')) == hash(id('a')) - hash(id('b'))
True
>>> id('a') - id('b')
1680
>>> hash(id('a')) - hash(id('b'))
1680

下面再多驗證幾個

>>> my_dict['c'] = 'cherry'
>>> hash(id('b')) - hash(id('c')) == hash(id('b')) - hash(id('c'))
True
>>> id('b') - id('c')
791760
>>> hash(id('b')) - hash(id('c'))
791760
>>> a['d'] = 'date'
>>> hash(id('d')) - hash(id('c')) == hash(id('d')) - hash(id('c'))
True
>>> id('d') - id('c')
1400
>>> hash(id('d')) - hash(id('c'))
1400

到這裡就可以證明上面的結論

為何計算的是 key 所在的記憶體地址的 hash 值?

比如上面的'a'(1 個字元) 明顯比其所在的記憶體地址 1290454097736(13 個字元)要短。短的計算不是更快嗎?記住一句話:Python 中一切皆物件,'a'是個 str 物件,1290454097736 是個 int 物件

>>> type('a')
<class 'str'>
>>> type(id('a'))
<class 'int'>

一個物件裡不是僅僅儲存對應值,它還有很多屬性(含方法),來看看誰的屬性多

>>> len(dir('a'))
77
>>> len(dir(id('a')))
70

str 物件比 int 物件多 7 個屬性

它們都有個叫 __sizeof__() 的魔法方法,用於獲取當前物件所佔用的記憶體空間大小(位元組)

>>> id('a').__sizeof__()
32
>>> 'a'.__sizeof__()
50

從上面可以發現:雖然 'a' 看起來只有 1 個字元,但其佔用的記憶體空間要大於其記憶體地址 id('a') 所佔用的空間

當然這不是主要原因,Python 直譯器會將其轉換為適當的資料型別再進行 hash 計算

不過,dict 的 key 不僅僅可以是 str 物件,也可以是 int、bytes、fromzenset 等這些可雜湊(hashable)物件,可雜湊物件都是不可變(immutable)物件(注意:反之不一定成立,如 tuple),不可變物件記憶體地址不變。大多數情況下,相比計算這些不同物件型別的 hash 值,直接計算物件所在記憶體地址(整數)的 hash 值效能更高,這也就是為什麼不是計算 key 的 hash 值,而是計算 key 所在記憶體地址的 hash 值

閱讀更多