接上一篇
Redis 哈希表的本質:數組里存的是什么
Redis 哈希表的核心——dictEntry
結構體,是真正承載我們存儲的鍵值對數據的那個結構。
它的定義非常簡潔,但設計得很巧妙。以下是其 C 語言代碼(在 Redis 源碼 src/dict.h
中):
typedef struct dictEntry {void *key; // 鍵union { // 值(是一個聯合體)void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next; // 指向下一個哈希表節點,形成鏈表
} dictEntry;
讓我們逐字段分解它的樣子和作用:
1. void *key
(鍵)
- 類型:
void *
,一個指向任意類型的指針。 - 作用: 存儲鍵(Key)。
- 細節: Redis 中的鍵絕大多數情況下都是 SDS (Simple Dynamic String) 類型。所以,這里通常指向一個 SDS 字符串的結構體。使用
void *
使得字典的實現非常通用,理論上可以存儲任何類型的鍵,但 Redis 自身主要用字符串作為鍵。
2. union v
(值 - 聯合體)
這是最精妙的部分。v
是一個聯合體(union)。聯合體的特點是所有成員共享同一塊內存空間,空間大小由最大的成員決定。這意味著,在任何一個時刻,這個字段只能存儲其中一種類型的值。
-
void *val
:- 這是最常用的成員。它是一個指針,指向 Redis 中表示的“值”對象。
- Redis 中的所有數據類型(字符串、列表、哈希、集合、有序集合等)在底層都是用
redisObject
結構體(robj
)來表示的。這個val
指針就指向一個robj
。 - 通過
robj
,Redis 才能知道這個值是什么類型(type)、用什么編碼(encoding),以及如何操作它。
-
uint64_t u64
:- 存儲一個 64 位無符號整數。
- 應用場景: 當哈希表被用于一些 Redis 的內部功能時,可以直接存儲整數,避免額外的指針開銷(既省內存,又免去了一次指針尋址,速度更快)。例如,用于記錄數據庫的過期時間戳。
-
int64_t s64
:- 存儲一個 64 位有符號整數。
- 應用場景: 同上,用于存儲整數值。
-
double d
:- 存儲一個 雙精度浮點數。
- 應用場景: 用于直接存儲浮點數值。
使用聯合體的好處:
- 節省內存:一塊內存,多種用途。如果不用聯合體,就需要為每種可能的值類型都定義一個字段,會浪費大量空間。
- 高效:對于整數和浮點數,可以直接內聯存儲,省去了創建
robj
對象和指針引用的開銷,性能更高。
3. struct dictEntry *next
(下一個節點指針)
- 類型: 指向另一個
dictEntry
結構體的指針。 - 作用: 用于解決哈希沖突,形成鏈表(拉鏈法)。
- 細節: 當兩個或多個鍵通過哈希函數計算出的數組索引(桶位置)相同時,就會發生哈希沖突。這些發生沖突的鍵值對會通過
next
指針連接成一個單向鏈表,掛在數組的同一個桶上。查找時,如果找到的桶里有多個節點,就需要遍歷這個鏈表,用key
來進行精確匹配。
一個具體的例子
假設我們執行命令 SET mykey "Hello"
,這個鍵值對在全局哈希表中就會由一個 dictEntry
來表示:
key
指針:指向一個 SDS 結構,里面存儲著字符串"mykey"
。v.val
指針:指向一個redisObject
(robj
)。- 這個
robj
的type
是OBJ_STRING
。 - 它的
ptr
指針又指向一個 SDS 結構,里面存儲著字符串"Hello"
。
- 這個
next
指針:假設"mykey"
沒有發生哈希沖突,那么這個指針就是NULL
。
內存結構簡化圖示:
dictEntry
+-------------------+ +-------------------------+
| key (void*) ---|---->| SDS: "mykey" |
+-------------------+ +-------------------------+
| v (union) | +-------------------------+
| val (void*) ---|---->| redisObject (robj) |
| u64 | | type: OBJ_STRING |
| s64 | | encoding: ... |
| d | | ptr (void*) -------+ |
+-------------------+ +-------------------------+ |
| next (NULL) | |
+-------------------+ |vSDS: "Hello"
總結
dictEntry
結構體是 Redis 所有鍵值數據的最終載體,它的設計體現了 Redis 對性能和內存效率的極致追求:
- 通用性:使用
void*
鍵和聯合體v
,使其能夠靈活存儲各種數據。 - 高效性:通過聯合體內聯存儲整數和浮點數,減少內存分配和指針跳轉。
- 解決沖突:通過
next
指針實現拉鏈法,簡單可靠。
理解 dictEntry
是理解 Redis 如何高效管理海量數據的關鍵一步。