Redis——字典
字典,又称为符号表、关联数组或映射(map),是一种用于保存键值对的抽象数据结构
在字典中,一个键key
映射一个值value
,通过key
来操作value
,因此字典中的key
必须是唯一的
在Redis中,字典被用于Redis数据库的底层实现,也就是说我们存储的五大Redis数据类型String,List,Set,Zset,Hash
都被存储在字典里,并且数据类型Hash
的底层实现之一也是字段
字典是实现
Redis的字典使用哈希表作为底层实现,一个哈希表里面有多个哈希表节点,每一个哈希节点保存了字典中的一个键值对(就是套娃)
- Redis的哈希表使用
dict.h/dictht
结构体定义
typedef struct dictdt {
//哈希表数组,类型为dictEntry
dictEntry **table;
//哈希表大小,即table数组的长度
unsigned long size;
//哈希表大小掩码,用于计算索引值
//sizemask的大小总是等于size-1,在Java中的HashMap结构中计算索引值使用table.length和hash(key)
//而Redis则是table.length-1和hash(key)
unsigned long sizemask;
//哈希表节点数量
unsigned long used;
}dictht;
- 哈希表节点使用
dictEntry
结构表示,每个dictEntry
结构都保存着一个键值对
typedef struct dictEntry {
//键,可以是任意类型(指向其他类型的一个指针)
void *key;
//值
//可以是任意类型,uint64_t整数、int64_t整数
union{
void *val;
uint64_t u64;
int64_t s64;
}v;
//保存该下一个节点的指针,解决哈希冲突
struct dictEntry *next;
}dictEntry;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rQFL6k9N-1632358186221)(images/dictht.png)]
- Redis中的字典由
dict.h/dict
结构表示
typedef struct dict {
//类型特定函数
//因为Redis中的数据库使用字典dict来存储数据,故字典中能存储String,List,Set等不同的数据类型
//而不同的数据类型的操作行为不同,那么久需要为特定的数据类型指定特定的操作函数
//每个dictType结构中保存了一簇用于操作特定类型键值对的函数
dictType *type;
//私有数据
//可选的传入特定类型操作函数的参数信息
void *privdata;
//哈希表
//哈希表定义数组长度为2的目的是在进行rehash时将哈希表中的键值对rehash到另一个数组中,起辅助作用
dictht ht[2];
//记录rehash时的索引下标,不进行rehash时为-1
//Redis的rehash采用渐进式rehash方式,通过trehashidx的值将该索引下的键值对rehash到ht[1]中
int trehashidx;
}dict;
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
...
}dictType;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TgXRt1w0-1632358186223)(images/dict.png)]
哈希冲突
Redis中解决哈希冲突采用的是链地址法,并且是头插入法,即新添加的节点插入到已有链表的头部,这样就可以将插入的时间复杂度控制在O(1)
rehash
当哈希表中的节点越来越多时,必将会造成更多的哈希冲突,从而增加链表的长度时查询的效率降低,因此在哈希节点达到某个条件时需要进行哈希表的扩充,或者当哈希节点减少时进行缩小,避免过多的节点都在一个索引下标中形成一个长链表,而其他索引下标没有节点导致的浪费,那么则需要进行rehash的过程
- 哈希表rehash的过程
- 为字典的ht[1]哈希表分配空间用于存储rehash后的键值对,而分配的空间大小取决于要执行的操作以及ht[0]当前包含的键值对数量(ht[0].used的值),并且保证哈希表的数组大小为
2的n次幂
- 扩展操作:ht[1]的空间大小为:
ht[1].length >= ht[0].used * 2
的2^n
- 缩小操作:ht[1]的空间大小为:
ht[1].length >= ht[0].used的
2^n`
- 将保存在ht[0]中的所有键值对rehash到ht[1]中
- 当ht[0]中的所有键值对都rehash到ht[1]后,释放ht[0]并将ht[1]设置为ht[0],再创建出一个新的空白的ht[1],为下一次rehash准备,也就是说所有的键值对操作一般都在ht[0]中
渐进式rehash
在将ht[0]中的键值对rehash到ht[1]中时,并不是一次性完成的,因为当ht[0]中的键值对的数量足够大时进行一次性rehash时十分耗时,而是采用渐进式rehash的方式逐渐将ht[0]中的键值对rehash的ht[1]中
- 为ht[1]分配内存空间,让字典同时持有两个数组ht[0]、ht[1]
- 在字典中维护一个索引计数器
rehashidx
,将他的值设置为0,并在每次rehash结束后自增1,直到ht[0]中没有键值对 - 在rehash进行期间,每次对字典执行添加、删除、查找等操作时,程序除了执行以上操作外,顺便将ht[0]中索引为rehashidx下的所有键值对rehash到ht[1]中,之后rehashidx自增1
- 随着字典操作次数的增加,rehashidx的值不断增大,渐渐将ht[0]中的键值对都rehash到ht[1]中,并且当执行的操作为添加时是直接在ht[1]数组中执行的,而其他操作为先到ht[0]中执行,如果ht[0]到没有要执行的键值对,则到ht[1]中查询
字典API
函数 | 作用 |
dictCreate | 创建一个新的字典 |
dictAdd | 将给定的键值对添加到字典里面 |
dictDelete | 从字典中删除指定的键值对 |
dictReplace | 替换键值对 |
dictFetchValue | 返回给定将的值 |
dictGetRandomKey | 随机返回字典中的键值对 |
dictRelease | 释放给定字典,即删除字典中的所有键值对 |