接上一篇
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 如何高效管理海量数据的关键一步。