字典又叫符号表、关联数组、映射(map),是一种用于保存键值对的抽象数据结构
字典中的每个键都是独一无二的,程序可以在字典中根据键查找、更新与之关联的值,或者删除整个键值对
运用
redis的数据库,对redis数据库增删改查都是对字典进行操作
另外也运用于哈希表的底层实现,当一个哈希值包含的键值对比较多或者键值对中的元素都是比较长的字符串时,redis就会使用字典作为哈希键的底层实现
实现
redis的哈希表
typedef struct dictht{
dictEntry **table;//哈希表数组的定义
unsigned long size;//哈希表的大小
unsigned long sizemask;//哈希表大小掩码,用于计算索引值,等于size-1
unsigned long used;//哈希表中已有的节点数量
}
哈希表节点dictEntry
typedef struct dictEntry{
void *key;//键
union {//值,可以是指针,或者是整数
void *val;
uint_tu64;
int64_ts64;
}
//指向下一个哈希表节点,形成链表
struct dictEntry *next;
}
注意:next属性是可以将多个哈希值相同的键值对连接在一起,以此来解决哈希冲突的问题
redis字典
typedef struct dict {
dictType *type;//类型特定函数
void * privdata;//私有数据
dictht ht[2];//哈希表
int rehashindex;//rehash索引,当rehash不再进行时,值为-1
}dict;
注意:type是一个指向dictType结构的指针,每个dictType结构保存了一套用于操作特定类型键值对的函数,redis会为用途不同的字典设置不同的类型特定函数;
一般只会用到ht[0]哈希表,而ht[1]只会在rehash的时候会用到;
rehashindex记录了rehash的进度,如果当前没有进行rehash的话,它的值为-1;
dictType结构定义
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;
redis的哈希算法
采用的是MurmurHash2,最新版本redis采用MurmurHash3,感兴趣自行百度
redis解决hash冲突的方式
redis的哈希表使用链地址法来解决键冲突,每个哈希节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来;补充:可以参考jdk1.7 hashmap中hash冲突的解决方式(数组+链表)
rehash
为什么要rehash?
哈希表保存的键值对数量太多或者数量太少,为了保持一个恰当的负载因子,程序需要对哈希表进行相应的扩容或者收缩操作。
过程
扩容:ht[1]的大小为2^n>=ht[0].used*2 (n从1开始,依次递增,取第一次满足条件的2^n)
收缩:ht[1]的大小为2^n>=ht[0].used
将h[0]上的所有键值对rehash到ht[1]上面,完毕后释放h[0],将ht[1]设置为ht[0],然后再ht[1]新建一个空白的哈希表,为下一次rehash做准备。
扩容的条件,满足任一条即可
1、服务器没有执行bgsave命令或者bgrewriteaof命令,负载因子>=1
2、服务器正在执行bgsave命令或者bgrewriteaof命令,负载因子>=5
负载因子=ht[0].used/ht[0].size
收缩
哈希表的负载因子<0.1
渐进式rehash
键值对数目巨多,rehash动作分多次、渐进式地完成,避免集中式rehash而带来的庞大计算量致使服务器停止服务
过程
- ht[1]分配内存空间,让字典同时持有ht[0]和ht[1]
- 字典中维持索引计数器变量rehashindex,并设置它的值为0,代表rehash工作即将开始
- 在rehash过程中,每次对字典执行添加、删除、查找、更新操作时,除了执行指定的操作外,还会将ht[0]哈希表在rehashindex索引上的所有键值对rehash到ht[1],当rehash工作完成后,程序将rehashindex的属性加1
- 随着字典的不断执行,最终ht[0]上的全部键值对将rehash到ht[1],这时将rehashindex的值设为-1,代表rehash操作结束
在渐进式rehash过程中注意
- 增删改查操作会在两个哈希表中进行
- 查找时,现在ht[0]上找,找不到再去ht[1]上找
- 新增加的键值对一律保存到ht[1]中,而ht[0]则不再进行任何添加操作,这一措施保证了ht[0]包含的键值对数量只会减而不会增,并随着rehash操作的执行最终变为空表。
字典API