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

糾正<儲存 dict 的元素前是計算 key 的 hash 值?>

前幾天發了一篇名為 儲存 dict 的元素前是計算 key 的 hash 值? 的文章,因缺乏相關的背景知識,導致得出了不正確的推論。那篇文章的推論是

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

簡單說是:儲存 dict 的元素前計算的是 key 所在記憶體地址的 hash 值上面的等式是成立的

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

但是需要糾正說明的是,我上面的那個推論是錯的!

等式成立的原因

這裡先說上面的等式為什麼成立,因為整數的 hash 值是其本身

>>> a
1234567
>>> hash(a)
1234567

又因為記憶體地址是個整數,所以記憶體地址的 hash 值也是其本身,即和記憶體地址一樣的值

>>> my_dict = {'a': 'apple', 'b': 'banana'}
>>> hash(id('a')) == id('a')
True
>>> id('a')
2673717403464
>>> hash(id('a'))
2673717403464

這就是為什麼這個等式成立

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

推論錯誤的原因

如果兩個值不同,那麼它們的記憶體地址也不同,這是正確的,但我錯誤的認為如果值相同,那麼記憶體地址也相同。下面是一個具有相同值不同記憶體地址的例子

>>> c = 'a-c'
>>> d = 'a-c'
>>> id(c) == id(d)
False
>>> id(c)
2673720167592
>>> id(d)
2673720167704
注:上面的 cd 雖然值是相同的,但它們不是同一個物件,所以記憶體地址不一樣

回到那個錯誤的推論:

儲存 dict 的元素前計算的是 key 所在記憶體地址的 hash 值

該推論成立的前提之一是:相同的 key 值的記憶體地址必須相同,但事實是像上面的例子一樣,相同的 key 值可以擁有不同的記憶體地址

假設該推論成立的話,就會導致 dict 中出現兩個相同的 key 值,但事實不是這樣的,即便記憶體地址不同,只要值相同就不可以同時作為 dict 的 key,後者會覆蓋前者。

>>> c
'a-c'
>>> d
'a-c'
>>> {c: 0, d: 1}
{'a-c': 1}

因為相同的 key 具有相同的 hash 值

>>> hash(c) == hash(d)
True
>>> hash(c)
-8124728931706162487
>>> hash(d)
-8124728931706162487

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

首先了解下關於 key 與其 hash 值之間的幾點事實:

  1. 相同的 key 肯定具有相同的 hash 值;

    應該不用解釋,這是 hash 演算法決定的;
  2. 不同的 key 也可能具有相同的 hash 值;

    因為 hash 演算法會將任何長度的資料轉換成一個固定長度的字串(int 物件除外),所以可能生成的 hash 值的數量是有限的,而可用來計算 hash 值的資料量理論上是無窮的,這就造成兩個資料的 hash 值可能相同
  3. 具有相同 hash 值的 key 不一定相同;

    原因正是因為不同的 key 可以具有相同的 hash 值
  4. 具有不同 hash 值的 key 肯定不同

    原因正是因為相同的 key 具有相同的 hash 值;

dict 是基於雜湊表的資料結構,雜湊表是一塊連續的記憶體空間(陣列),因為所儲存的 key-value 散落在不同的位置上,key-value 之間存在大量的空白空間是很常見的,所以雜湊表又稱散列表。dict 本質就是一個能利用雜湊函式將 key 轉化為索引的陣列(雜湊函式+陣列),雜湊函式的功能之一是將 key 值轉換為陣列索引(dict 的 key 具有唯一性的本質是陣列索引的唯一性)。在轉換的過程中,需要對 key 進行 hash 值計算,計算 hash 值的目的是為了確定 key 應該儲存在陣列中的哪個位置(索引),即定位,而不是判斷兩個 key 是否相同。因為通過比較 hash 值是無法判斷兩個 key 是否相同的(參考前面的第 3 點事實),所以當 hash 值相同時,會定位到相同的表元(索引對應的元素),該表元裡的 key 是否與計算的 key 相等還需要進一步判斷。

反正儲存 dict 的元素前還是計算 key 的 hash 值,但這只是雜湊函式中的其中一個過程或步驟。

閱讀更多