Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解

文章目录

  • 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中的字典/哈希表,包括字典的结构和操作等。
Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解_第1张图片
图1 哈希表(Hash Table)

1. 字典的结构

Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解_第2张图片

示意图中,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;
  1. dictEntryKey 结构体

    ``dictEntryKey 结构体用于存储键的信息,包括指向键的指针和键的哈希值。其中,key 成员变量是指向键的指针,hash` 成员变量是键的哈希值。

  2. dictEntry 结构体

    ``dictEntry 结构体用于表示字典中的一个节点,包含键值对的信息和指向下一个节点的指针。其中,key成员变量是dictEntryKey 结构体类型,表示键的信息;v成员变量是一个union,可以存储不同类型的值,包括指向值的指针、64 位整数、64 位有符号整数和双精度浮点数等;next` 成员变量是指向下一个节点的指针。

  3. dictType 结构体

    ``dictType` 结构体用于定义字典的类型,包括哈希函数、键复制函数、值复制函数、键比较函数、键销毁函数和值销毁函数。

    • hashFunction 成员变量是哈希函数,用于计算键的哈希值;
    • keyDup 成员变量是键复制函数,用于复制键的值;
    • valDup 成员变量是值复制函数,用于复制值的值;
    • keyCompare 成员变量是键比较函数,用于比较两个键是否相等;
    • keyDestructor 成员变量是键销毁函数,用于销毁键的值;
    • valDestructor 成员变量是值销毁函数,用于销毁值的值。
  4. dictht 结构体

    ``dictht 结构体用于表示哈希表,包括哈希表的数组、大小、掩码和已使用的节点数。其中,table 成员变量是指向哈希表数组的指针,size 成员变量是哈希表数组的大小,sizemask成员变量是掩码(等于size - 1),用于计算哈希值对应的数组下标,used` 成员变量是哈希表已使用的节点数。

  5. 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详解和使用示例》
在这里插入图片描述

你可能感兴趣的:(redis,数据结构,缓存,nosql)