文章目录
- 0.前言
- 1. 字典的结构
- 2. 源码解析
- 2.1. 字典的结构体
- 2.2. 字典的函数接口
- dictAdd
- dictFind
- dictResize
- 3. 字典/哈希表的优缺点
- 3.1 优点
- 3.1.1. 快速的查找时间
- 3.1.2. 动态调整大小
- 3.1.3. 灵活的数据类型
- 3.2 缺点
- 4.总结
- 5. Redis从入门到精通系列文章
0.前言
上个篇章回顾,我们上个章节,讲了Redis中的快表(QuickList),它是一种特殊的数据结构,用于存储一系列的连续节点,每个节点可以是一个整数或一个字节数组。快表是Redis中的底层数据结构之一,常用于存储有序集合(Sorted Set)等数据类型的底层实现。
那么本章讲解Redis中的底层数据结构中的字典(Dictionary)也称为哈希表(Hash Table)。字典(Dictionary)是一种高效的数据结构,用于存储键值对,常用于实现哈希表。用于快速存取和查找数据的数据结构,它的内部实现是一个数组加上链表或者红黑树。在 Redis 中,字典被广泛用于实现哈希表键值对的存储和查找,例如 Redis 中的 Hash 类型就是基于字典实现的在本文中,我们将深入了解Redis中的字典/哈希表,包括字典的结构和操作等。
Redis中的字典(Dictionary)是一种高效的数据结构,用于存储键值对,常用于实现哈希表(Hash Table)。在本文中,我们将深入了解Redis中的字典/哈希表,包括字典的结构和操作等。
图1 哈希表(Hash Table)
1. 字典的结构
示意图中,RedisDictionary 表示 Redis 字典,它包含了多个 HashSlot 对象,每个 HashSlot
对象代表一个哈希槽,它包含了多个 Entry 对象,每个 Entry 对象代表一个键值对。
Redis中的字典(Dictionary)是由多个哈希表(Hash Table 如图1)组成的,每个哈希表都包含了多个哈希槽(Hash Bucket)。哈希表的结构如下图所示:
+---------+---------+---------+-------+
| bucket | bucket | bucket | ... |
+---------+---------+---------+-------+
| ... | ... | ... | ... |
+---------+---------+---------+-------+
其中,bucket是哈希桶,包含了多个键值对。每个键值对包含了一个键和一个值,键和值可以是任意数据类型,但键必须是唯一的。哈希表的大小是固定的,当哈希桶中的键值对数量超过一定阈值时,需要对哈希表进行扩容操作,以保持哈希表的高效性。
Redis 6 字典的源码实现主要在 dict.h 和 dict.c 文件中。dict.h 文件定义了字典的结构体和函数接口,而 dict.c 文件则具体实现了这些函数接口。
2. 源码解析
2.1. 字典的结构体
Redis 6 字典的结构体定义与 Redis 5 相同,但是新增了一个 dictEntryKey
结构体,用于存储键的信息。Redis 6 字典的结构体定义如下:
typedef struct dictEntryKey {
void *key; // 指向键的指针
uint64_t hash; // 哈希值
} dictEntryKey;
typedef struct dictEntry {
dictEntryKey key; // 键的信息
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
typedef struct dictType {
uint64_t (*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;
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
-
dictEntryKey
结构体``dictEntryKey
结构体用于存储键的信息,包括指向键的指针和键的哈希值。其中,
key成员变量是指向键的指针,
hash` 成员变量是键的哈希值。 -
dictEntry
结构体``dictEntry
结构体用于表示字典中的一个节点,包含键值对的信息和指向下一个节点的指针。其中,
key成员变量是
dictEntryKey结构体类型,表示键的信息;
v成员变量是一个
union,可以存储不同类型的值,包括指向值的指针、64 位整数、64 位有符号整数和双精度浮点数等;
next` 成员变量是指向下一个节点的指针。 -
dictType
结构体``dictType` 结构体用于定义字典的类型,包括哈希函数、键复制函数、值复制函数、键比较函数、键销毁函数和值销毁函数。
hashFunction
成员变量是哈希函数,用于计算键的哈希值;keyDup
成员变量是键复制函数,用于复制键的值;valDup
成员变量是值复制函数,用于复制值的值;keyCompare
成员变量是键比较函数,用于比较两个键是否相等;keyDestructor
成员变量是键销毁函数,用于销毁键的值;valDestructor
成员变量是值销毁函数,用于销毁值的值。
-
dictht
结构体``dictht
结构体用于表示哈希表,包括哈希表的数组、大小、掩码和已使用的节点数。其中,
table成员变量是指向哈希表数组的指针,
size成员变量是哈希表数组的大小,
sizemask成员变量是掩码(等于
size - 1),用于计算哈希值对应的数组下标,
used` 成员变量是哈希表已使用的节点数。 -
dict
结构体``dict
结构体用于表示字典,包括字典的类型、私有数据、哈希表、重哈希索引和正在运行的迭代器数量。其中,
type成员变量是指向字典类型的指针,
privdata成员变量是指向私有数据的指针,
ht成员变量是一个长度为 2 的
dictht数组,用于实现哈希表的渐进式重哈希操作;
rehashidx成员变量表示正在进行重哈希的哈希表索引,如果
rehashidx等于 -1,表示没有进行重哈希操作;
iterators` 成员变量表示正在运行的迭代器数量,用于控制在迭代器运行期间进行重哈希操作。
2.2. 字典的函数接口
Redis 6 字典提供了与 Redis 5 相同的函数接口,但是在实现细节上有一些变化。
dictAdd
Redis 6 相对于 Redis 5 做了一些优化,其中 dictAdd
函数的实现发生了变化。在 Redis 6 中,当向字典中添加一个新的键值对时,会先尝试将该键的哈希值计算出来,并查找哈希表中是否已经存在该哈希值。如果该哈希值还没有节点使用,那么就可以直接将新的键值对插入到哈希表中。如果该哈希值已经存在节点,那么就需要使用键比较函数来比较新键和旧键是否相等。如果新键和旧键相等,那么就需要用新值替换旧值;否则就需要将新键值对插入到哈希槽的链表末尾。
在实现过程中,Redis 6 使用了一个 dictEntryKey
结构体来存储键的信息,包括指向键的指针和键的哈希值。这样可以避免重复计算键的哈希值,提高了添加操作的效率。
dictFind
在 Redis 6 中,dictFind
函数的实现与 Redis 5 相同,但是在查找过程中,Redis 6 使用了 dictEntryKey
结构体中的哈希值来进行比较,以提高查找效率。
dictResize
Redis 6 中的 dictResize
函数与 Redis 5 相同,但是在实现过程中,Redis 6 增加了一些控制逻辑,以避免在短时间内多次调整哈希表的大小,从而提高了性能。
Redis 6 字典相对于 Redis 5 在实现细节上进行了一些优化,主要是在添加和查找操作上进行了改进,提高了字典的性能和效率。其中,添加操作使用了一个
dictEntryKey
结构体来存储键的信息,以避免重复计算键的哈希值;查找操作使用了dictEntryKey
结构体中的哈希值来进行比较,以提高查找效率。此外,Redis 6 还增加了一些控制逻辑,以避免在短时间内多次调整哈希表的大小,从而提高了性能。
如果有想继续链接Redis6源码的同学,可以参考github地址 https://github.com/redis/redis/tree/6.0
,可以从这个 Github 仓库中获取整个 Redis 6 的代码。在该仓库中,可以找到所有与 Redis 相关的代码,包括服务器端、客户端、测试代码等等。如果需要了解 Redis 6 的源码实现,可以通过该仓库中的代码来深入了解 Redis 6 的实现细节。同时,Redis 官方也提供了详细的文档和说明,可以帮助开发者更好地理解 Redis 6 的设计思路和代码实现。
3. 字典/哈希表的优缺点
3.1 优点
3.1.1. 快速的查找时间
提供了常数时间复杂度O(1)的键值对查找,这意味着查找与字典大小无关,即使对于非常大的字典,查找时间也非常快。
3.1.2. 动态调整大小
字典/哈希表支持动态调整大小以适应数据的增长或减少。当某个哈希桶中的键值对数量超过一定阈值时,Redis会自动调整哈希桶的大小,以确保字典的高效性。这意味着Redis可以处理大量的数据而不会导致显著的性能损失。
3.1.3. 灵活的数据类型
可以存储任意数据类型的键和值,这使它非常灵活。这意味着它可以用于各种应用场景,从简单的键值存储到更复杂的数据结构。
3.2 缺点
- 哈希表的空间利用率可能较低,当哈希桶中的键值对数量较少时,会浪费一定的空间。
- 哈希表的扩容和缩容可能会造成性能损失,因为需要重新计算哈希值、重新分配内存等操作。
- 哈希表中的键的顺序是无序的,不适合存储需要按照键的顺序进行访问的数据。
4.总结
字典/哈希表适合存储大量的键值对,并需要快速地查找键对应的值的场景。在实际应用中,需要根据具体的业务场景选择合适的底层数据结构。例如,如果需要按照键的顺序进行访问,可以使用有序集合(Sorted Set)等其他数据结构。
5. Redis从入门到精通系列文章
《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》