图解Redis之数据结构篇——字典 (2)

    对于散列表来说,负载因子过大或过小都不好,负载因子过大,散列表的性能会下降。而负载因子过小,则会造成内存不能合理利用,从而形成内存浪费。因此我们为了保证负载因子维持在一个合理的范围内,要对散列表的大小进行收缩或扩展,即rehash。散列表的rehash过程类似于数组的收缩与扩容。

1.3.4 开放寻址法与链表法比较

    对于开放寻址法解决冲突的散列表,由于数据都存储在数组中,因此可以有效地利用 CPU 缓存加快查询速度(数组占用一块连续的空间)。但是删除数据的时候比较麻烦,需要特殊标记已经删除掉的数据。而且,在开放寻址法中,所有的数据都存储在一个数组中,比起链表法来说,冲突的代价更高。所以,使用开放寻址法解决冲突的散列表,负载因子的上限不能太大。这也导致这种方法比链表法更浪费内存空间。

    对于链表法解决冲突的散列表,对内存的利用率比开放寻址法要高。因为链表结点可以在需要的时候再创建,并不需要像开放寻址法那样事先申请好。链表法比起开放寻址法,对大装载因子的容忍度更高。开放寻址法只能适用装载因子小于1的情况。接近1时,就可能会有大量的散列冲突,性能会下降很多。但是对于链表法来说,只要散列函数的值随机均匀,即便装载因子变成10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多。但是,链表因为要存储指针,所以对于比较小的对象的存储,是比较消耗内存的,而且链表中的结点是零散分布在内存中的,不是连续的,所以对CPU缓存是不友好的,这对于执行效率有一定的影响。

二、Redis字典 2.1 Redis字典的实现

    Redis字典使用散列表最为底层实现,一个散列表里面有多个散列表节点,每个散列表节点就保存了字典中的一个键值对。

2.1.1 字典 typedef struct dict{ //类型特定函数 void *type; //私有数据 void *privdata; //哈希表-见2.1.2 dictht ht[2]; //rehash 索引 当rehash不在进行时 值为-1 int trehashidx; }dict;

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

type属性是一个指向dictType结构的指针,每个dictType用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。

privdata属性则保存了需要传给给那些类型特定函数的可选参数。

typedef struct dictType { //计算哈希值的函数 unsigned int (*hashFunction) (const void *key); //复制键的函数 void *(*keyDup) (void *privdata,const void *key); //复制值的函数 void *(*keyDup) (void *privdata,const void *obj); //复制值的函数 void *(*keyCompare) (void *privdata,const void *key1, const void *key2); //销毁键的函数 void (*keyDestructor) (void *privdata, void *key); //销毁值的函数 void (*keyDestructor) (void *privdata, void *obj); }dictType;

ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表, 一般情况下,字典只使用ht[0] 哈希表, ht[1]哈希表只会对ht[0]哈希表进行rehash时使用。

rehashidx记录了rehash目前的进度,如果目前没有进行rehash,值为-1。

2.1.2 散列表 typedef struct dictht { //哈希表数组,C语言中,*号是为了表明该变量为指针,有几个* 号就相当于是几级指针,这里是二级指针,理解为指向指针的指针 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 unsigned long sizemask; //该哈希已有节点的数量 unsigned long used; }dictht;

table属性是一个数组,数组中的每个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对

size属性记录了哈希表的大小,也是table数组的大小

used属性则记录哈希表目前已有节点(键值对)的数量

sizemask属性的值总是等于 size-1(从0开始),这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面(索引下标值)。

2.1.3 散列表节点 //哈希表节点定义dictEntry结构表示,每个dictEntry结构都保存着一个键值对。 typedef struct dictEntry { //键 void *key; //值 union{ void *val; uint64_tu64; int64_ts64; }v; // 指向下个哈希表节点,形成链表 struct dictEntry *next; }dictEntry;

key属性保存着键值中的键,而v属性则保存着键值对中的值,其中键值(v属性)可以是一个指针,或uint64_t整数,或int64_t整数。 next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,解决键冲突问题。

2.2 Redis如何解决散列冲突 2.2.1 链表法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppspy.html