1.概述
字典dict相当于java中的HashMap,用来实现redis数据库和redis的hash类型value。
2.字典的实现
字典数据结构分为三部分,字典dict、哈希表dictht、节点dictEntry。一个dict包含两个dictht,一个dictht有多个dictEntry,每个dictEntry代表key-val。
2.1 源码
/*
* 哈希表
*
* 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
*/
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于 size - 1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
} dictht;
/*
* 字典
*/
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 当 rehash 不在进行时,值为 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
// 目前正在运行的安全迭代器的数量
int iterators; /* number of iterators currently running */
} dict;
/*
* 哈希表节点
*/
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
/*
* 字典类型特定函数
*/
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;
2.2 hash算法
2.3 键冲突
当有两个及以上的键被分配到hash数组的同一个索引上面,程序将新添加的节点添加到链表的表头位置。
2.4 rehash
随着操作的不断执行,hash表的键值对的数量太多或者太少,程序需要对hash表的大小进行扩展和收缩。
扩展操作:ht[1]的大小等于ht[0].used*2的2的n次方幂
2.5 渐进式rehash
rehash并不是一次性、集中式的完成,而是分多次、渐进式的完成。这么做的原因是hash表的key可能数据量特别大,如果一次性rehash到另外一个ht的话,可能会让导致服务在一段时间停止。
1)dict字典中维护一个索引计数器变量rehashindex,并将它的值设置为0,表示rehash正式开始
2)在rehash期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定操作,还会进行rehash操作。当rehash执行完成之后,rehashindex+1
3)最终在某个时间点上,rehash完成,rehashindex=-1
注:因为rehash过程会存在两个hash表,所以要查找一个键,程序会先从ht[0]查找,如果没找到就从ht[1]查找。新添加的键值都会保存在ht[1]
3.总结
感觉redis的hash表就跟java的HashMap差不多