Redis中的字典
原文連結:https://www.changxuan.top/?p=1122
簡介
字典是一種在 Redis 中高頻使用的用於儲存鍵值對的抽象資料結構,在 Java 中常用的有 HasmMap
等。
由於字典中鍵的唯一性,所以在 Redis 中得到了廣泛的應用。
實現
Redis 中的字典是基於雜湊表
(dictht, dict hash table)實現的,雜湊表中的每個節點儲存一個鍵值對。雜湊表的結構體定義如下:
typedef struct dictht {
// 雜湊表陣列
dictEntry **table;
// 雜湊表大小
unsigned long size;
// 雜湊表大小掩碼,用於計算索引值 size - 1,用來計算鍵值對放在哪個索引上
unsigned long sizemask;
// 雜湊表已有節點的數量
unsigned long used;
} dictht;
雜湊表節點 dictEntry
的結構則如下所示:
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
}v;
// 指向下個雜湊表節點
struct dictEntry *next;
} dictEntry;
dictEntry
中的值有些特別,它表示其值有可能是一個指標或者是一個 uint64_t
整數,或者是一個 int64_t
整數。
因為存在 next
屬性,很顯然它是使用鏈地址法
解決的雜湊鍵衝突。
接下來我們看一下字典(dict)的定義:
typedef struct dict {
// 型別特定函式
dictType *type;
// 私有資料
void *privdata;
// 雜湊表
dictht ht[2];
// rehash 索引 當不在進行 rehash 的時候,值為-1
int trehashids;
} dict;
屬性 type
是一個指向 dictType
結構體的指標,每個 dictType
儲存了一些用於操作特定型別鍵值對的函式。
typedef struct dictType {
// 計算雜湊值的函式
unsigned int (*hashFunction)(const void *key);
// 複製鍵的函式
void *(*keyDup)(void *privdata, const void *key);
// 複製值的函式
void *(*valDup)(void *privdata, const void *obj);
// 對比鍵的函式
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 銷燬鍵的函式
void (*valDestructor)(void *privdata, void *obj);
} dictType;
ht
陣列表示儲存兩個雜湊表,平常情況下只使用 ht[0]
,只有在 rehash
時才會使用到 h[1]
和 trehashids
。
字典的結構就是,一個字典中有兩個雜湊表,平時只用一個雜湊表。另一個雜湊表在 rehash 的時候使用。每個雜湊表中存在一個節點陣列,節點則用於存放鍵值對。
新增鍵值對
新增鍵值對就意味著需要計算鍵的雜湊值,從而得出索引值。根據索引值將鍵值對的雜湊節點放到雜湊表的指定位置上。計算雜湊值使用的是字典結構體中的 type
中的函式,即 hash = dict->type->hashFunction(key)
。計算索引值則是 index = hash & dict->ht[x].sizemask
,x 取決於當前使用的是ht[1]還是ht[2]。
不過,總會有不同的鍵對應相同的索引值,產生衝突。Redis 中使用了常用的“鏈地址法”來解決這個問題,當出現衝突時就把新節點放到表頭的位置。
Rehash
隨著字典中鍵值對數量的不斷變化,為了保證雜湊表的空間利用率以及效率,在雜湊表過大或者過小是要對雜湊表大小進行調整。如果過小,則會不斷髮生鍵衝突導致效率低下,如果過大則會浪費儲存空間。所以,經過不斷調整可以使其維持在一個合理的範圍。
步驟
-
為
ht[1]
分配空間,大小取決於是擴大雜湊表還是縮小雜湊表。如果擴大,其大小為第一個大於等於ht[0].used * 2
且同時為2的n次方冪
的值。如果縮小,其大小為第一個大於等於ht[0].used
其同時為2的n次方冪
的值。 -
將儲存在
ht[0]
中所有的鍵值對重新計算雜湊值和索引值後,存放在ht[1]
中。 -
當遷移完所有的鍵值之後,釋放原
ht[0]
的空間,將原h[1]
改為h0
, 並在ht[1]
新建立一個空白雜湊表。
那麼何時擴充套件雜湊表大小呢? 一是當沒有在執行 BGSAVE
或者 BGREWRITEAOF
命令時,並且雜湊表的負載因子大於等於1時。 二是當在執行這倆命令,但是負載因子大於等於5時(節約記憶體,上述兩命令消耗記憶體)。
負載因子計算公式為:負載因子 = 雜湊表儲存節點數量/雜湊表大小
那麼何時縮小雜湊表大小呢? 當雜湊表負載因子小於 0.1 時則會進行縮小。
漸進式 Rehash
其實對於上述步驟 2 ,普通人覺得這不就是把鍵值對重新分配一下嗎?但是如果此時存在百萬、千萬甚至億級的鍵值對時,恐怕就是不是一眨眼的功夫就可以完成的了。如果非得一次性完成,那麼可能會導致伺服器的不可用。所以為了解決這個問題,Redis 採用了慢慢來的辦法漸進式 Rehash。
其主要步驟與前面的有些相似,只不過在漸進式Rehash中使用到了 dict->trehashids
值來記錄當前rehash到了哪個索引。在 Rehash 期間,可以對字典正常進行增加、刪除、查詢和更新。然後同時也會將 trehashids
上記錄的索引值上的節點遷移到 h[1]
上。並且所有的新增節點都會放到 h[1]
中,這樣就會導致 h[0]
中的節點越來越少,最終完成 rehash。其它的操作則會在兩個表上進行。
相關文章
- Redis資料結構詳解(2)-redis中的字典dictRedis資料結構
- Redis 字典結構細談Redis
- Python中的字典Python
- 《閒扯Redis七》Redis字典結構的底層實現Redis
- Python中遍歷字典以及字典中的鍵和值Python
- 跋山涉水 —— 深入 Redis 字典遍歷Redis
- Redis學習筆記(三) 字典Redis筆記
- Python中字典的操作Python
- [Redis原始碼閱讀]dict字典的實現Redis原始碼
- python-字典-如何取出字典中的所有值Python
- Redis設計於實現之字典Redis
- Redis 字典結構實現分析BTRedis
- Redis 設計與實現 4:字典Redis
- 【Redis 系列】redis 學習十六,redis 字典(map) 及其核心編碼結構Redis
- 列表與字典中的坑
- python中的集合與字典Python
- 《閒扯Redis八》Redis字典的雜湊表執行Rehash過程分析Redis
- Redis 物件內部組織結構 —— 字典Redis物件
- python中的字典是什麼Python
- Python中字典dictPython
- Redis資料結構—連結串列與字典的結構Redis資料結構
- 【最完整系列】Redis-結構篇-字典Redis
- Redis資料結構—連結串列與字典Redis資料結構
- Python中字典和json的區別!PythonJSON
- WPF中資源字典(ResourceDictionary)的使用
- Python中的字典遍歷有序嗎?Python
- Python中判斷字典的值常用的方法!Python
- 2.1.3 CDB中的資料字典結構
- 阿里面試官:HashMap 熟悉吧?好的,那就來聊聊 Redis 字典吧!阿里面試HashMapRedis
- Python中字典使用詳解Python
- Redis 中的原子操作(1)-Redis 中命令的原子性Redis
- 字串形式的列表,字典轉列表,字典字串
- Golang 中字典的 Comma Ok 是如何實現的Golang
- docker中的redisDockerRedis
- redis字典快速對映+hash釜底抽薪+漸進式rehash | redis為什麼那麼快Redis
- python中列表、字典和字串的互相轉換Python字串
- Redis 的基礎資料結構(一) 可變字串、連結串列、字典Redis資料結構字串
- 玩轉python字典與列表(中)Python