Redis-数据结构-字典
来源:互联网 发布:电影《美国黑帮》知乎 编辑:程序博客网 时间:2024/06/04 20:51
字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增删改查操作也是构建在字典的操作之上的。
Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
1、哈希表dict.h/dictht
typedef struct dictht { //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 //总是等于size-1 unsigned long sizemask; //该哈希表已有节点的数量 unsigned long used;}dictht;table属性是一个数组,数组中的每个元素都指向一个哈希表节点dict.h/dictEntry结构(键值对)的指针;
2、哈希表节点dict.h/dictEntry
typedef struct dictEntry { //键 void *key; //值 union{ void *val; uint64_t u64; int64_t s64; }v; //指向下个哈希表节点,形成链表 struct dictEntry *next;} dictEntry;3、字典dict.h/dict
typedef struct dict { //类型特定函数 dictType *type; //私有数据 void *privdata; //哈希表 dictht ht[2]; //rehash索引 //当rehash不再进行时,值为-1 int trehashidx;/*rehashing not in progress if rehashidx == -1*/}dict;typedef struct dicType { //计算哈希表的函数 unsigned int (*hashFunction)(const void *key); //复制键的函数 void *(*keyDup)(void *privdata,const void *key); //复制值的函数 void *(*valDup)(void *privdata,const void *obj); //对比键的函数 int (*keyCompare)(void *privada,const void *key1, const void *key2); //销毁键的函数 void (*keyDestructor)(void *privdata, void *key); //销毁值的函数 void (*valDestructor)(void *privdata, void *obj);}
哈希表算法
当要将一个新的键值对添加到字典里面时,程序会根据键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上.
Redis 计算哈希值和索引的方法如下:
hash = dict->type->hashFunction(k);index = hash & dict->ht[0].sizemask
假设,要将上图中键值对 k1和v1添加到字典中,使用 hashFunction
计算出 k1 的哈希值为9,那么
index = 9 & 3 = 1;
Redis 使用 MurmurHash2 算法 来计算键的哈希值.
解决键冲突
当有两个或两个以上的键被分配到了哈希表数组的同一索引上时,称这些键发生了冲突( collision
)
Redis 的哈希表使用链地址法来解决冲突,每个哈希表节点都有一个 next
指针,多个哈希表节点可以用 next
指针构成一个单项链表,被分配到同一个索引上的多个节点可以用这个对单向链表连接起来,这就解决了键冲突的问题.
Rehash
随着操作的不断进行, 哈希表保存的键值对会逐渐地增多或减少,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的大小进行相应的扩展或者收缩.这个过程叫做rehash
.
Redis 对字典的哈希表执行 rehash 的步骤如下:
为字典的
ht[1]
哈希表分配空间,空间的大小取决于要执行的操作,以及ht0]
当前包含的键值对数量( used 属性值):如果执行的是扩展操作,那么
ht[1]
的大小为第一个大于等于ht0].used*2
的 $2^n$ .如果执行的是收缩操作,那么
ht[1]
的大小为打一个大于等于ht[0].used
的$2^n$.
将保存在
ht[0]
中所有键值对rehash
到ht[1]
上面: 任何事指的是重新计算键的哈希值和索引值,然后键键值对放到ht[1]
哈希表的指定位置.当
ht[0]
包含的所有键值对都迁移到了ht[1]
之后, 释放ht[0]
, 再将ht[1]
设置为ht[0]
,并在ht[1]
后面创建一个空白的哈希表.
举个例子,假设程序要对下图的 `ht[0] 进行扩展操作
ht[0].used 当前值为4 , $2^3$ 恰好是第一个大于等于 4*2 的值,所以 ht[1] 哈希表的大小设置为8,下图展示了 ht[1] 分配了空间之后字典的样子.
将 ht[0] 包含的四个键值对 rehash 到 ht[1], 图下图所示:
释放 ht[0], 将 ht[1] 设置为 ht[0]. 再分配一个空哈希表. 哈希表的大小由原来的4 扩展至8.
渐进式 rehash
上一节说过, 扩展或收缩哈希表需要将 ht[0]
里的所有键值对 rehash 到ht[1]
中,但是这个 rehash
动作并不是一次性,集中式完成的,而是分多次,渐进式完成的.
这么做的原因是,当哈希表里保存的键值对多至百万甚至亿级别时,一次性地全部 rehash 的话,庞大的计算量会对服务器性能造成严重影响.
以下是渐进式 rehash 的步骤:
为
ht[1]
分配空间在字典中维持一个索引计数器变量
rehashidx
, 将它的值设置为0,表示 rehash 正式开始在 rehash 进行期间,每次对字典进行增删改查时,顺带将 ht[0] 在
rehashidx
索引上的所有键值对 rehash 到 ht[1] 中,同时将rehashidx
加 1.随着操作不断进行,最终在某个时间点上,
ht[0]
所有的键值对全部 rehash 到ht[1]
上,这时将rehashidx
属性置为 -1,表示 rehash操作完成.
在渐进式 rehash 执行期间,新添加到字典的键值对一律保存到 ht[1]
里,不会对ht[0]
做添加操作,这一措施保证了 ht[0]
只减不增,并随着 rehash 进行, 最终编程空表.
渐进式的 rehash 避免了集中式 rehash 带来 的庞大计算量和内存操作.
- Redis数据结构之字典
- redis数据结构之字典
- Redis-数据结构-3-字典
- Redis-数据结构-字典
- Redis数据结构-字典
- redis内部数据结构之字典
- Redis数据结构(二)字典
- 【Redis基本数据结构】字典实现
- Redis底层数据结构之字典
- Redis内部数据结构详解之字典(dict)
- 《Redis源码学习笔记》数据结构-字典
- Redis内部数据结构详解之字典(dict)
- Redis内部数据结构详解之字典(dict)
- redis内部数据结构详解之字典dict
- 【Redis源码剖析】 - Redis内置数据结构之字典dict
- 【Redis源码剖析】 - Redis内置数据结构之字典dict
- 【Redis源码剖析】 - Redis内置数据结构之压缩字典zipmap
- Redis源码分析(三)——Redis数据结构-字典
- 581. Shortest Unsorted Continuous Subarray
- [Leetcode]_40 Combination Sum II
- CreateFile、ReadFile、WriteFile和fread、fwrite两种读写文件的方法
- 移动前端开发的一些简单分类!
- 【C++心路历程37】钓鱼(刷表法dp)
- Redis-数据结构-字典
- Java-剑指offer-正方体的三面和相等
- 如何判断是输入流还是输出流
- IOS UIScrollView用法总结
- 把set作为vector的元素
- [Leetcode]69. Sqrt(x)解析@Python
- 【git】git初学习
- SpringMVC中的多文件上传
- 回溯法-bfs--迷宫问题的最短路径