字典:(符号表)
字典就是一个存储kv的存储结构,类似与c++的map,redis数据库的底层就是使用字典实现的
除了数据库,字典也是哈希键的底层实现
字典使用哈希表实现,哈希表中存储的都是kv结构
typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used; } dictht;
sizemask和哈希值一起决定了这儿节点应该放在哪里,我们每一个哈希表节点都有一个next属性,这个可以解决链表冲突的问题,使得多个键值一样的可以连在一起
下面我们看一下哈希表节点的定义:
typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,形成链表 struct dictEntry *next; } dictEntry;
下面是字典的定义:
type主要是针对不同的类型,private是针对函数的参数
其中计算哈希值的函数就在type里面指向的
有个哈希表数组,ht[1]只有rehash的时候使用,rehashindex也是rehash的时候使用
typedef struct dict { // 类型特定函数 dictType *type; // 私有数据 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 当 rehash 不在进行时,值为 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ } dict;
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;
当加入一个键值的时候,我们先根据type里面的函数计算出哈希值,&mask计算出索引值,加入哈希表的指定索引中,
为了解决哈希表的冲突,我们使用拉链发,但是为了考虑效率,我们通常将新加入的节点放在最前面,不yongO(N)掺入
rehash:
哈希表的键值会不听的增多减少,为了让负载因子,维持在一个合理的范围,我们需要适当的进行扩展和收缩
- 为字典的
ht[1]
哈希表分配空间, 这个哈希表的空间大小取决于要执行的操作, 以及ht[0]
当前包含的键值对数量 (也即是ht[0].used
属性的值): -
- 如果执行的是扩展操作, 那么
ht[1]
的大小为第一个大于等于ht[0].used * 2
的 2^n (2
的n
次方幂); - 如果执行的是收缩操作, 那么
ht[1]
的大小为第一个大于等于ht[0].used
的 2^n 2
- 如果执行的是扩展操作, 那么
- 将保存在
ht[0]
中的所有键值对 rehash 到ht[1]
上面: rehash 指的是重新计算键的哈希值和索引值, 然后将键值对放置到ht[1]
哈希表的指定位置上。 - 当
ht[0]
包含的所有键值对都迁移到了ht[1]
之后 (ht[0]
变为空表), 释放ht[0]
, 将ht[1]
设置为ht[0]
, 并在ht[1]
新创建一个空白哈希表, 为下一次 rehash 做准备。
哈希表的扩展和收缩的条件:
1:如果没有执行BSAVE或者BGREWRITEAOF,并且负载因子大于等于1
2:如果执行BSAVE或者BGREWRITEAOF,并且负载因子大于等于5
这样设计是因为如果执行的话,会fork出新的进程,因为遵循写实复制,为了尽量避免写入内存进行复制,所以将负载因子提高一些
如果负载因子小0.1执行收缩
渐进事rehash:
因为哈希表的数据可能特别的多,所有rehash不是一次完成的,是多次分批完成的,这里就用到了reashindex,最开始rehashindex=0,表示对索引值0指向的复制,结束了,开始索引值1的,rehashindx+1,这个过程中如果查找的话,会先查找ht[0]->ht[1],添加的话都会添加大1里面,这样可能保证服务器正常的运作