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

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

驗證

這裏先建立個字典segmentfault

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

因爲哈希表是一塊連續的內存空間(數組),在不考慮 hash 值衝突的狀況下,若是計算的是 key 的 hash 值,那麼:'a' 的 hash 值與 'b' 的 hash 值之間的差值'a' 的內存地址與 'b' 的內存地址之間的差值(可理解爲內存地址裏的距離) 相等纔對,也就是說如下的等式成立纔對數組

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

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

>>> 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 值(這種說法不夠嚴謹),那計算的是什麼呢?code

計算的是 key 所在內存地址的 hash 值

在不考慮 hash 衝突的狀況下, 'a' 所在內存地址的 hash 值與 'b' 所在內存地址的 hash 值之間的差值'a' 的內存地址與 'b' 的內存地址之間的差值 相等,也就是說如下的等式成立纔對對象

hash(id('a')) - hash(id('b')) == id('a') - id('b')
>>> hash(id('a')) - hash(id('b')) == id('a') - 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')) == id('b') - id('c')
True
>>> id('b') - id('c')
791760
>>> hash(id('b')) - hash(id('c'))
791760
>>> a['d'] = 'date'
>>> hash(id('d')) - hash(id('c')) == id('d') - id('c')
True
>>> id('d') - id('c')
1400
>>> hash(id('d')) - hash(id('c'))
1400

到這裏就能夠證實上面的結論get

爲什麼計算的是 key 所在的內存地址的 hash 值?

好比上面的'a'(1 個字符) 明顯比其所在的內存地址 1290454097736(13 個字符)要短。短的計算不是更快嗎?
記住一句話:Python 中一切皆對象,'a'是個 str 對象,1290454097736 是個 int 對象hash

>>> 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 值

閱讀更多

相關文章
相關標籤/搜索