Redis字典这个数据结构,最重要的地方就是用于主数据库的K-V数据存储
redis中的字典与java中的HashMap一模一样,不过java8之后链表变成了红黑树,而redis中并没有这样做,依然是个链表
redis的字典结构中,key可以是字符串,整数,浮点,但无论redis客户端set一个什么类型的key(字符串,整数,浮点),最终到redis服务端的时候,都是字符串的形式,也就是说,key的类型都是字符串,不可能有其他类型,而value可以是String,Hash,List,Set,SortedSet
Redis自带客户端就是使用times 33散列函数来计算字符串的Hash值,Redis服务端的Hash函数使用的是siphash算法,主要功能与客户端Hash函数类似,其优点是针对有规律的键计算出来的Hash值也具有强随机分布性,但算法较为复杂
查找key:先hash一下key,然后找到位置,如果该位置存在多个元素,那么就轮询这多个元素,对比key,如果key相等,就找到了该元素
下面是字典的结构体代码,字典中包含多个hash表
typedef struct dict {
//该字典对应的特定操作函数,文末有介绍
dictType *type;
//该字典依赖的数据
void *privdata;
//Hash表,键值对存储在此
dictht ht[2];
//rehash标识。默认值为-1,代表没进行rehash操作;不为-1时,代表正进行rehash操作,存储的值表示Hash表ht[0]的rehash操作进行到了哪个索引值
long rehashidx;
//当前运行的迭代器数
unsigned long iterators;
} dict;
下面是客户端Hash表结构体代码,Hash表中包含多个dictEntry(键值对)
typedef struct dictht {
//指针数组,用于存储键值对,注意这里是2个*
dictEntry **table;
//table数组的大小
unsigned long size;
//恒定=size-1
unsigned long sizemask;
//table数组已存元素个数,包含next单链表的数据
unsigned long used;
} dictht;
注意sizemask:初始的时候认为设定Hash表的数组容量初始值为4,随着键值对存储量的增加,就需对Hash表扩容,新扩容的容量大小设定为当前容量大小的一倍,也就是说,Hash表的容量大小只能为4,8,16,32…。而sizemask掩码的值就只能为3,7,15,31…,对应的二进制为11,111,1111,11111…,因此掩码值的二进制肯定是每一位都为1。
如果没有sizemask,假设hashCode的大小是10,容量是4,那么获取hash的方式就是10除以4余2,所以hash=2,
现在有sizemask了,假设hashCode的大小是10,容量是4,sizemask则等于3,然后将hashCode和sizemask进行与操作,也就是10&3=2,所以hash=2
以上两种方式结果相同,但是计算机中,与运算要比求余快很多,redis这个算法还是很巧妙的
下面是dictEntry结构体
typedef struct dictEntry {
void *key; /*存储键*/
union {
void *val; /*db.dict中的val*/
uint64_t u64;
int64_t s64; /*db.expires中存储过期时间*/
double d;
} v; /*值,是个联合体*/
struct dictEntry *next; /*当Hash冲突时,指向冲突的元素,形成单链表*/
} dictEntry;
Hash表中元素结构体和我们前面自定义的元素结构体类似,整体占用24字节,key字段存储的是键值对中的键。v字段是个联合体,存储的是键值对中的值,在不同场景下使用不同字段。例如,用字典存储整个Redis数据库所有的键值对时,用的是*val字段,可以指向不同类型的值;再比如,字典被用作记录键的过期时间时,用的是s64字段存储;当出现了Hash冲突时,next字段用来指向冲突的元素,通过头插法,形成单链表。
下面是dictType结构体
typedef struct dictType {
//该字典对应的Hash函数
uint64_t (*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字典这个数据结构,除了主数据库的K-V数据存储外,还有很多其他地方会用到。例如,Redis的哨兵模式,就用字典存储管理所有的Master节点及Slave节点;再如,数据库中键值对的值为Hash类型时,存储这个Hash类型的值也是用的字典。在不同的应用中,字典中的键值对形态都可能不同,而dictType结构体,则是为了实现各种形态的字典而抽象出来的一组操作函数。
日记:
1.Redis字典这个数据结构,最重要的地方就是用于主数据库的K-V数据存储
2.巧妙的sizemask算法