Redis核心数据结构之字典(一)

字典

概述

字典又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种保存键值对(key-value pair)的抽象数据结构,在字典中,一个键(key)可以和一个值(value)进行关联(或者说将键映射为值),这些关联的键和值就称为键值对。字典中的每个键都是独一无二的,程序可以在字典中根据键查找与之关联的值,或者通过键来更新值,又或者根据键来删除整个键值对等等

字典经常作为一种数据结构内置在很多高级编程语言里面,但Redis所使用的C语言并没有内置这种数据结构,因此Redis构建了自己的字典实现。字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增删改查操作也是构建在对字典的操作之上的。

除了用来表示数据库之外,字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。

除了用来实现数据库和哈希键之外,Redis的不少功能也用到了字典

例子

  • 举个例子,当我们执行代码中的命令之后,会在数据库中创建一个键为"msg",值为"hello world"的键值对,这个键值对就是保存在代表数据库的字典里面的。
c 复制代码
redis> SET msg "hello world"
OK
  • 举个例子,website是一个包含3个键值对的哈希键,这个哈希键的键都是一些数据库的名字,而键的值就是数据库的主页网址:如代码所示。website键的底层实现就是一个字典,字典中包含了3个键值对,例如:Redis-Redis.io、MariaDB-MariaDB.orgMongoDB-MongoDB.org
c 复制代码
redis> HLEN webiste
(integer) 3
redis> HGETALL website
1) "Redis"
2) "Redis.io"
3) "MariaDB"
4) "MariaDB.org"
5) "MongoDB"
6) "MongoDB.org"

字典的实现

Redis的字典使用哈希表作为子层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对

哈希表

Redis字典所使用的哈希表由dict.h/dictht结构定义:如代码所示

table属性是一个数组,数组中的每个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对。size属性记录了哈希表的大小,也即是table数组的大小,而used属性则记录了哈希表目前已有节点(键值对)的数量。sizemask属性的值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面。

c 复制代码
typedef struct dictht {
 // 哈希表数组
 dictEntry **table;
 // 哈希表大小
 unsigned long size;
 // 哈希表大小掩码,用于计算索引值
 // 总是等于size -1
 unsigned long sizemask;
 // 该哈希表已有的数量
 unsigned long used;
}dictht;

这是一个大小为4的空哈希表(没有包含任何键值对)

哈希表节点

哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对,如代码所示.key属性保存着键值对中的键,而v属性保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整数,又或者是一个int64_t整数。next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键冲突(collision)的问题

c 复制代码
typedef struct dictEntry {
 // 键
 void *key;
 union {
  void *val;
  uint64_t u64;
  int64_t s64;
 }v;
 
 // 指向下个哈希表节点,形成链表
 struct dictEntry *next;

} dictEntry;
例子
  • 举个例子,该图就是通过next指针,将两个索引值相同的键K1和K0连接在一起的

字典

Redis中的字典由dict.h/dict结构表示:如代码所示。

type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:

  • 1.type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对
    的函数,Redis会为用途不同的字典设置不同的类型特定函数。
  • 2.而privdata属性则保存了需要传给那些类型特定函数的可选参数。
c 复制代码
typedef struct dict {
 // 类型特定函数
 dictType *type;
 // 私有数据
 void *privdata;
 // 哈希表
 dictht ht[2];
 // rehash索引
 // 当rehash不在进行时,值为-1
 int trehashidx; // rehasing not in progress if rehashidx == -1
}dict;

ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。除了ht[1]之外,另一个和rehash有关的属性就是rehashidx,它记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1.

c 复制代码
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 *key);
 // 销毁值的函数
 void (*valDestructor)(void *privdata, void *obj);
}dictType;

展示了一个普通状态下(没有进行rehash)的字典

哈希算法

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据

索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。

Redis计算哈希值和索引值的方法如下:

#使用字典设置的哈希函数,计算键key的哈希值

c 复制代码
hash = dict->type->hashFunction(key);

#使用哈希表的sizemask属性和哈希值,计算出索引值

#根据情况不同,ht[x]可以是ht[0]或者ht[1]

c 复制代码
index = hash & dict->ht[x].sizemask;

当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值的,这种算法的优点在于,即使输入的键是有规律的,算法仍能给出一个很好的随机分布性,并且算法的计算速度也非常快。

例子

举个例子,如果要将一个键值对K0和V0添加到字典里面,

那么程序会使用语句

c 复制代码
hash = dict->type->hashFunction(k0);

计算k0的哈希值.假设计算得出的哈希值为8,那么程序会继续使用语句:

c 复制代码
index = hash & dict->ht[0].sizemask = 8 & 3 = 0;

计算出键K0的索引值0,这表示包含键值对K0和V0的节点应该被放置到

哈希表数组真的索引0位置上,如图所示

相关推荐
编程爱好者熊浪1 天前
两次连接池泄露的BUG
java·数据库
cr7xin1 天前
缓存三大问题及解决方案
redis·后端·缓存
TDengine (老段)1 天前
TDengine 字符串函数 CHAR 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
qq7422349841 天前
Python操作数据库之pyodbc
开发语言·数据库·python
姚远Oracle ACE1 天前
Oracle 如何计算 AWR 报告中的 Sessions 数量
数据库·oracle
Dxy12393102161 天前
MySQL的SUBSTRING函数详解与应用
数据库·mysql
码力引擎1 天前
【零基础学MySQL】第十二章:DCL详解
数据库·mysql·1024程序员节
杨云龙UP1 天前
【MySQL迁移】MySQL数据库迁移实战(利用mysqldump从Windows 5.7迁至Linux 8.0)
linux·运维·数据库·mysql·mssql
l1t1 天前
利用DeepSeek辅助修改luadbi-duckdb读取DuckDB decimal数据类型
c语言·数据库·单元测试·lua·duckdb
睡前要喝豆奶粉1 天前
在.NET Core Web Api中使用redis
redis·c#·.netcore