当我们使用 Redis 的 Hash 操作时,底层的实现就是字典。
在介绍字典之后,我们先回忆一下 Redis 中的 Hash 操作。最常用的就是 HSET 和 HGET 了
127.0.0.1:6379> HSET user name sherlock (integer) 1 127.0.0.1:6379> HSET user age 20 (integer) 1 127.0.0.1:6379> HGET user name "sherlock" 127.0.0.1:6379> HGET user age "20" 127.0.0.1:6379>除了 HSET 和 HGET 外的常见指令还有:HDEL、HEXISTS、HGETALL、HMGET 等等,这里就不一一列举了,Redis 的 Hash 操作一般都是以 H 开头的。
我们可以看到,Hash 操作可以保存很多组键值对,其底层的视线就是字典
2、dict字典的定义在源码目录下 src/dict.h 文件中,为了便于理解,我们从最基本的结构往上介绍
2.1、dictEntry首先是 dictEntry,它表示字典中的一组键值对,声明如下:
typedef struct dictEntry { void *key; //键 union { void *val; uint64_t u64; int64_t s64; double d; } v; //值 struct dictEntry *next; //指向下个键值对 } dictEntry;
key 表示的键值对的键;
v 表示键值对的值,v 是一个共同体,表示这里的值类型可以是指针、uint64_t、int64_t 和 double 其中之一,用共同体可以节约内存;
dictEntrynext 指向下一组键值对,这里是链表,当需要存储的键值对最后计算得到的存储的位置索引出现重复的时候,就使用链表,将多个键值对存在一个数组元素中,而且,Redis 中,新数据会存储在链表的最前面
2.2、dicthtdictht 即为 Redis 操作时的值结构,用于保存多组的键值对,声明如下:
typedef struct dictht { dictEntry **table; //键值对数组,数组的元素是个链表 unsigned long size; //键值对数组的大小 unsigned long sizemask; //掩码,用于计算索引 unsigned long used; //键值对数量 } dictht;table 是一个 dictEntry 类型的数组指针,它的每个元素都是指针,都指向一个 dictEntry 类型;
size 表示键值对数组的大小;
sizemask 为掩码,用于计算键值对插入时的数组索引,它总是 size - 1,后面会再次说到;
used 表示当前哈希表存储的键值对的数量;
下图是一个 dictht 的存储结构,k0和k1的键值计算的索引相同,所以放在一个数组元素中:
2.3、dictdict 是最终的字典的数据结构,声明如下:
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; typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict;
dictType 是一组操作函数指针,用于操作特定类型的键值对,Redis 会为不同类型的键值对设置不同类型的函数;
privdata 表示需要传递给操作函数的特定私有数据;
ht 是一个 dictht 类型的数组,有两个元素,之所以两个,是因为需要rehash,后面会再次说到;
rehashidx 表示 rehash 进度,-1表示当前并没有进行 rehash;
iterators 表示当前运行的迭代器数量,本次不做特别说明;
下图表示一个没有在rehash的字典
3、字典的存储方法Redis 中的哈希操作,顾名思义,存储方法肯定和哈希算法有关,这里先简单介绍一下哈希算法。
3.1、哈希算法哈希算法又称之为散列函数,它把任意长度的输入,通过一系列的算法, 变换成固定长度的输出。
Redis 底层使用的哈希算法是 MurmurHash 算法,最初由 Austin Appleby 在2008 年发明,其优势在于,无论输入是否有规律,输出都是随机分布的,而且速度很快。
其实了解哈希的同学都能够明白,用有限的hash值来表示无限的数据,肯定会出现不同的数据得出重复的哈希值的问题,当然,专业的说法不叫重复,叫 碰撞。
3.2、存储过程和键冲突