Redis源码学习四、字典

Redis源码学习四、字典

字典介绍

字典,又称符号表(symbol table)、关联数组或映射(map),是一种用于保存键值对(key-value)的抽象数据结构。

在字典中,一个键(key)可以和一个值(value)进行关联(或者说将键值对隐射为值),这些关联的键和值就称为键值对。

字典中的每个键都是独一无二的,程序可以在字典中根据键查找与之关联的值,或者通过键来跟新值,又或者根据键来删除整个键值对,等等。

字典经常作为一种数据结构内置在很多高级语言里面,但Redis所使用的C语言并没有内置这种数据结构,因此Redis构建了自己的字典实现。

字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增删改查操作也是构建在对字典的操作之上的。

举个例子:当我们执行命令:

redis>SET msg "hello world"

ok

在数据库中创建了一个键为“msg”, 值为“hello world”的键值对时,这个键值对就是保存在代表数据库的字典里面的。

除了用于表示数据库之外,字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。

举个例子:website是一个包含了10086个键值对的哈希键,这个哈希键的键都是一些数据库的名字,而键的值就是数据库的主页地址。

redis>HLEN website

(integer)10086

redis>HGETALL website

1)"Redis"

2)"Redis.io"

3)"MongoDB"

4)"MongoDB.org"

.....

website的底层实现就是一个字典,字典包含了10086个键值对。

字典的实现

Redis的字典实现使用哈希表作为底层实现,一个哈希表里面可以包含有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

哈希表

Redis使用的哈希表由dict.h/dictht结构体定义:
typedef struct dictht
{
	//哈希表数组
	dictEntry **table;
	
	//哈希表大小
	unsigned long size;
	
	//哈希表大小掩码,用于计算索引值
	//总是等于size-1
	unsigned long sizemask;
	
	//哈希表已有节点的数量
	unsigned long used;
}dictht;
table是一个数组,用于指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对。size属性记录了哈希表的大小,也即table数组的大小,而used属性则记录了哈希表目前已有节点(键值对的数量)。sizeMask属性的值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面。
Redis源码学习四、字典_第1张图片

哈希表节点

哈希表节点使用dicrEntry结构表示,每个dictEntry结构都保存着一个键值对:
typedef struct dictEntry
{
	//键
	void *key;

	//值
	union{
		void *val;
		uint64_tu64;
		int64_ts64;
	}v;

	//指向下一个哈希表节点,形成链表
	struct dictEntry *next;
}dictEntry;
key属性保存着键值对中的键,而v属性则保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整数,又或者是一个int64_t整数。
next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,因此来解决键冲突(collision)的问题。Redis源码学习四、字典_第2张图片

字典

typedef struct dict
{

	//特定类型函数
	dictType *type;

	//私有数据
	void *privadata;

	//哈希表
	dictht ht[2];

	//rehash索引
	//当rehash不在进行时,值为-1
	rehashindex = -1;
}dict;
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
而privdata属性则保存了需要传给那些类型特定函数的可选参数。
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;
ht属性是一个包含了两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
除了ht[1]之外,另一个和rehash有关的属性就是rehashindex,它记录了rehash目前的速度,如果目前没有在进行rehash,那么它的值为-1.
Redis源码学习四、字典_第3张图片

哈希算法

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。

Redis计算哈希值和索引值的方法如下:
#使用字典设置哈希函数,计算键key的哈希值
hash = dict->type->hashFunction(key);
#使用哈希表的sizemask属性和哈希值,计算出索引值
#根据情况不同,ht[x]可以是ht[0]或者ht[1]
index = hash & dict->ht[x].sizemask;

当字典被用作数据库的底层实现或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算哈希键的哈希值。
MurmurHash算法最初是由Austin Appleby于2008年发明,这种算法的有点在于,即使输入的键是有规律的,算法仍能给出一个很好的随机分分布性,并且算法的计算速度也非常快。

你可能感兴趣的:(Redis源码学习四、字典)