dict
Redis字典是应用了哈希表作为底层实现的,一个哈希表里能够有多个哈希表节点,每一个哈希表节点保留字典的一个键值对。
/* Hash Tables Implementation. * * This file implements in-memory hash tables with insert/del/replace/find/ * get-random-element operations. Hash tables will auto-resize if needed * tables of power of two in size are used, collisions are handled by * chaining. See the source code for more information... :) * * 这个文件实现了一个内存哈希表, * 它反对插入、删除、替换、查找和获取随机元素等操作。 * * 哈希表会主动在表的大小的二次方之间进行调整。 * * 键的抵触通过链表来解决。
数据结构
字典
/* * 字典 */typedef struct dict { // 类型特定函数 dictType *type; // 公有数据 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 当 rehash 不在进行时,值为 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 目前正在运行的平安迭代器的数量 int iterators; /* number of iterators currently running */} dict;
/* * 字典类型特定函数 */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;
void *provdata 保留须要传给那些类型特定函数的可选参数。
个别状况下字典只应用ht[0] ht[1]只会对ht[0]进行rehash时应用。
哈希表
/* * 哈希表 * * 每个字典都应用两个哈希表,从而实现渐进式 rehash 。 */typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used;} dictht;
哈希表节点
/* * 哈希表节点 */typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,造成链表 struct dictEntry *next;} dictEntry;
V保留的是值,它能够是一个指针,也能够是有符号和无符号的整型
long int就是int 都是4字节
不同名是因为起因是晚期的C编译器定义了long int占用4个字节,int占用2个字节
long long才是8字节
留神
uint8_t,uint16_t,uint32_t等都不是什么新的数据类型,它们只是应用typedef给类型起的别名
#ifndef __int8_t_defined# define __int8_t_definedtypedef signed char int8_t; typedef short int int16_t;typedef int int32_t;# if __WORDSIZE == 64typedef long int int64_t;# else__extension__typedef long long int int64_t;# endif#endif /* Unsigned. */typedef unsigned char uint8_t;typedef unsigned short int uint16_t;#ifndef __uint32_t_definedtypedef unsigned int uint32_t;# define __uint32_t_defined#endif#if __WORDSIZE == 64typedef unsigned long int uint64_t;#else__extension__typedef unsigned long long int uint64_t;
哈希算法
要将一个新的键值对退出字典,程序须要限依据键计算出哈希值和索引值,而后依据索引值,将蕴含新键值对的哈希表节点放到哈希数组的指定索引上。
解决建抵触
当两个或者以上数量的键被哈希表数组分到了同一个索引上这就时抵触
Redis的解决办法是 链地址法
每个哈希表节点都有一个next指针,多个节点能够用next指针形成一个单向链表,被分到同一个索引的多个节点就能够用单向链表连起来。
rehash
随着零碎的运行,哈希表保留的键值会越来越多,为了让哈希表的负载因子维持在一个正当的范畴,程序须要对哈希表的大小进行响应的弹性变动。
简略的说,如果哈希表超过里的元素超过了某个大小,就会给h[1]调配空间,就把h[1]扩充为h[0]的两倍,而后再把h[0]中的元素复制到h[1]中,再把h[0]清空,最初把h[1]变为h[0],h[0]变为h[1].
触发扩缩容的条件
扩容
- 以后服务器没有执行BGSAVA和BGREWRITEAOF,并且哈希表的负载因子大于等于1
- 以后服务器正在执行BGSAVA和BGREWRITEAOF,并且哈希表的负载因子大于等于5
#负载因子计算load_facotr = ht[0].used / h[0].size
再执行BGSAVA和BGREWRITEAOF命令的过程中,Redis须要创立以后服务器过程的子过程,而大多数操作系统都是采纳写时复制(copy-on-write)技术来优化子过程的应用效率,所以在子过程存在期间,服务器会提供执行拓展所需的负载因子,从而防止不必要的内存写入
当负载因子下于0.1主动膨胀。
渐近式rehash
如果哈希表里的数据过多,咱们是不可能短时间进行rehash,那太占用服务器资源了,所以咱们能够分屡次渐进式的实现。
给h[1]调配空间,在字典中保护一个索引计数器rehashidx,初始值为0。
在rehash期间,每次对字典进行了增删改查时,还会顺带将h[0]哈希表在rehashidx的索引上的索引键值对rehash到h[1]中,当rehash实现,rehashidx +1
在最终某个时候,h[0]的索引键值对都会被rehash到ht[1],这时rehashidx -1
留神
这里就有一个问题那么在rehash时,整个数据时散布在h[0]和h[1]上都有的,那么这里咱们规定了程序会先去h[0]找再去h[1]找。
那么再rehash时新增加的数据去哪呢?那必定时间接去h[1]保障h[0]只会缩小最初为空。