rehash
上一章節有提到,dict
的ht
是一個陣列,僅有兩個dictht
,內容介紹了dictht
的組成和關係。接下來的內容解釋了為什麼要有兩個dictht
,原因可以總結為因為rehash
。
為什麼要有 rehash
隨著操作的不斷執行,雜湊表 ( dictht )
中儲存的鍵值對會增多或減少,如果過多或過少,負載因子就會超出合理的範圍。為了讓雜湊表的負載因子維持在一個合理的範圍,就需要對雜湊表進行rehash
,達到根據雜湊表的大小進行相應的擴充套件或收縮。
dict
字典中有dictht
屬性,是一個陣列,ht[1]
則是用於rehash
的。
未進行rehash
時dict
的結構圖
rehash
擴容的過程簡述
- 將
ht[1]
的table
新建一個dictEntry
陣列(擴容必然大於ht[0]
中table
陣列的容量)。 - 將
ht[0]
中的鍵值對重新以ht[1]
中的屬性計算雜湊和索引值,對映到ht[1].table.dictEntry
中的雜湊節點中,並將ht[0]
中dictEntry
陣列索引指向的dictEntry
設定為Null
。 - 釋放
ht[0]
,並將ht[1]
設定為ht[0]
,然後為ht[1]
為配一個空的雜湊表(即table
指向NULL
)。
ht[1]
的擴容演算法:
x = ht[0].used * 2
。n = 2 的多少次方(while2的次方不斷累加)
。
在n
不斷的計算過程當中,當n
第一次大於等於x
時。n
即為ht[1]
的容量。例:
當ht[0].used
等於4
時。x = 8
。n
的冪不斷 + 1
,導致n
不斷變大,冪等於2
時,n
等於4
。n
未大於等x
。冪繼續+1
,等於3
,n
這時等於8
,大於等於x
。這時取8
作為ht[1]
的size
。
ht[1]
的收縮演算法:
同擴容演算法大概一致。不同的地方在於x
的計算。收縮演算法中ht[0].used=x
。即不用乘以2
。
擴容第二步結果圖
由上一圖作為 rehash 基礎作為演示:
rehash
觸發條件
負載因子計算方式:load_factor = ht[0].used / ht[0].size
收縮觸發條件:負載因子小於0.1
時。
擴容觸發條件:
- 當前沒有執行
bgsave
或bgrewriteaof
命令,且雜湊表負載因子>=1
。 - 當前正在執行
bgsave
或bgrewriteaof
命令,且雜湊表負載因子>=5
。
漸進式的rehash
rehash
為什麼是漸進式的?
如果雜湊表的資料量很大,rehash
的佔用會很高,涉及到雜湊計算和索引計算,資料轉移等。所以rehash
需要分多次,漸進式的完成。
它是如何進行的?
它將rehas
h的操作平攤到每一次對該字典的增刪改查的操作上,即每次進行增刪改查,都會進行一部分的rehash
操作,並將rehasidx
修改。這就是漸進式,避免了集中式rehash
帶來的龐大計算量。
上一圖可見,rehashidx
值為3,表明了正在進行rehash
。那為什麼是3
而不是2
,rehashidx
還代表著什麼?
rehashidx代表ht[0].table這個陣列中的索引,意味著當前rehash已經進行到了哪個索引了。3則意為著索引為3的鍵值對已轉移完畢。
當鍵值對全部轉移完畢後。rehashidx
會重新歸於-1
。
當rehashidx不等於-1時,這個時候查詢一個鍵值對,會怎樣?
不等於-1
,則意味著正在進行rehash
,會先在ht[0]
中查詢,沒找到就會在ht[1]
中查詢。
當進行rehash
時,新增一個鍵值對會怎樣?
會直接新增到ht[1]
中。
本作品採用《CC 協議》,轉載必須註明作者和本文連結