Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每一个哈希表节点就保存了字典中的一个键值对
- typedef struct dictht{
- //哈希表数组
- dictEntry **table;
- //哈希表大小
- unsigned long size;
- //哈希表大小掩码,用于计算索引值
- //总是等于size-1
- unsigned long sizemask;
- //该哈希表已有节点的数量
- unsigned long used;
- }dictht;
table
属性是一个数组, 数组中的每个元素都是一个指向 dict.h/dictEntry
结构的指针, 每个 dictEntry
结构保存着一个键值对。
size
属性记录了哈希表的大小, 也即是 table
数组的大小, 而 used
属性则记录了哈希表目前已有节点(键值对)的数量。
sizemask
属性的值总是等于 size - 1
, 这个属性和哈希值一起决定一个键应该被放到 table
数组的哪个索引上面。
- typedef struct dictEntry {
-
- // 键
- void *key;
-
- // 值
- union {
- void *val;
- uint64_t u64;
- int64_t s64;
- } v;
-
- // 指向下个哈希表节点,形成链表
- struct dictEntry *next;
-
- } dictEntry;
key
属性保存着键值对中的键, 而 v
属性则保存着键值对中的值, 其中键值对的值可以是一个指针, 或者是一个 uint64_t
整数, 又或者是一个 int64_t
整数。
next
属性是指向另一个哈希表节点的指针, 这个指针可以将多个哈希值相同的键值对连接在一次, 以此来解决键冲突(collision)的问题。
举个例子, 图 4-2 就展示了如何通过 next
指针, 将两个索引值相同的键 k1
和 k0
连接在一起。
- typedef struct dict {
-
- // 类型特定函数
- dictType *type;
-
- // 私有数据
- void *privdata;
-
- // 哈希表
- dictht ht[2];
-
- // rehash 索引
- // 当 rehash 不在进行时,值为 -1
- int rehashidx; /* rehashing not in progress if rehashidx == -1 */
-
- } dict;
type
属性和 privdata
属性是针对不同类型的键值对, 为创建多态字典而设置的:
type
属性是一个指向 dictType
结构的指针, 每个 dictType
结构保存了一簇用于操作特定类型键值对的函数, Redis 会为用途不同的字典设置不同的类型特定函数。privdata
属性则保存了需要传给那些类型特定函数的可选参数。- 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 (*keyDestructor)(void *privdata, void *key);
-
- // 销毁值的函数
- void (*valDestructor)(void *privdata, void *obj);
-
- } dictType;
ht
属性是一个包含两个项的数组, 数组中的每个项都是一个 dictht
哈希表, 一般情况下, 字典只使用 ht[0]
哈希表, ht[1]
哈希表只会在对 ht[0]
哈希表进行 rehash 时使用。
除了 ht[1]
之外, 另一个和 rehash 有关的属性就是 rehashidx
: 它记录了 rehash 目前的进度, 如果目前没有在进行 rehash , 那么它的值为 -1
。
采用的是链地址法
举个例子, 假设程序要将键值对 k2
和 v2
添加到下图 所示的哈希表里面, 并且计算得出 k2
的索引值为 2
, 那么键 k1
和 k2
将产生冲突, 而解决冲突的办法就是使用 next
指针将键 k2
和 k1
所在的节点连接起来, 如图 下下图所示。