一.概述
接着上篇继续,这篇把数据结构之字典学习完, 这篇知识点包括:哈希算法,解决键冲突, rehash , 渐进式rehash,字典API。
1.1 哈希算法
当一个新的键值对 需要添加到字典里面时,程序需要先根据“键值对”的键计算出哈希值和索引值,再根据索引值,将包含新“键值对”的哈希表节点放到哈希表数组的指定索引上面。redis计算哈希值和索引值的方法如下:
#使用字典设置的哈希函数,计算键key的哈希值
hash = dict -> type->hashFunction(key);
# 使用哈希表的sizemask属性和哈希值,计算出索引值, ht[x] 可以是ht[] 或者ht[]
index = hash & dict->ht[x].sizemask;
例1: 将一个“键值对”K0和V0 添加到字典里面,那么程序会使用如下语句,假设hash变量的哈希值为8, sizemask为3, &是指"按位与"运算符。公式如下:
hash = dict -> type->hashFunction(K0);
index= hash $ dict ->ht[]. sizemask= & = ;
通过公式计算出健K0的索引值为0,这个新的“键值对”节点应该放置到哈希表数组的索引0位置,对于哈希算法的底层实现,redis使用MurmurHash2算法来计算键的哈希值。Murmur哈希是一种非加密散列函数,目前有三个版本(MurmurHash1、MurmurHash2、MurmurHash3),这里不在深入,例1如下图所示:
1.2 解决键冲突
当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,称为键冲突。Redis 的哈希表使用“链地址”来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到哈希表数组同一个索引上,用单向链表把多个节点连接一起,解决了键冲突问题。
例2: 程序要将新"键值对"k2和v2 添加到哈希表中,计算的k2 索引值为2, 但该哈希表数组索引值2上已有"键值对"k1和v1。 解决键索引冲突的办法就是使用next指针将键k2和键k1的节点连接起来,如下图所示:
1.3 rehash 重新散列
随着对哈希表数组的不断操作, 哈希表数组保存的"键值对"节点会增多或减少,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的“键值对”数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或收缩。扩展或收缩哈希表的工作是通过执行rehash 操作来完成的,Redis字典的哈希表执行reash的步骤如下:
(1) 为字典的ht[1] 哈希表分配空间。这个空间大小取决于要执行的操作,以及ht[0]当前包含的“键值对”数量(ht[0].used的值)
(2) 将保存在ht[0]中的所有“键值对”rehash到ht[1]上面, rehash指的是重新计算键的哈希值和索引值,然后“键值对”放置到ht[1]哈希表的指定位置上。
(3) 当ht[0]包含的所有“键值对”都迁移到了ht[1]之后,释放ht[0], 将ht[1]设置为ht[0],并在ht[1]新创建一个空白的哈希表,为下一次rehash准备。
例3: 下面是对ht[0]进行扩展操作,在没有rehash之前,字典如下所示
ht[0].used当前的值为4(扩展公式为ht[0].used *2, 2^3次方), 程序会将ht[1]哈希表的大小设置为8,ht[1]在分配空间之后,字典如下所示:
将ht[0]包含的四个“键值对”都rehash到ht[1],此时释放了ht[0]的哈希表,如下图所示:
最后将ht[1] 设置为ht[0], 然后为ht[1]分配一个空白哈希表,至此,对哈希表的扩展操作执行完毕,程序成功将哈希表的大小从原来的4改为了现在的8,如下图所示:
总结:哈希表的扩展与收缩。当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:
(1) 服务器目前没有在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于1。
(2) 服务器目前正在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于5。
#负载因子= 哈希表已保存节点数量 / 哈希表大小
load_factor=ht[].used /ht[].size
例如:对于一个大小为4,包含4个“键值对”的哈希表来说,这个哈希表的负载因子为: load_factor= 4/4=1。 又例如,对于一个大小为512,包含256个“键值对“的哈希表来说,这个哈希表的负载因子为: load_factor=256/512=0.5(这个要不需要扩展)。另外当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。
1.4 渐进式 rehash
上面rehash扩展或收缩哈希表需要将ht[0] 里面的所有”键值对“ rehash到ht[1]里面, 这个rehash动作并不是一次性,集中式完成的,而是分多次,渐进式完成的。这样做原因在于考虑到有大量”键值对“,如果一次性将大量”键值对“全部rehash到ht[1]的话,庞大的计算量可能会导致服务器在一段时间内停止服务。因此是渐进式的将ht[0]里面的键值对慢慢地rehash到ht[1]。
例4 以下是哈希表渐进式rehash的详细步骤:
(1) 为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。此时rehashidx值为-1。
(2) 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始(rehash了一个键值对)。此时rehashidx值为0。
(3) 在每次rehash进行期间,除了对字典执行操作,程序还会将ht[0]的rehashidx索引值增一,下图是全部rehash完成,此时rehashidx值为3。
(4) 最后当ht[0]的所有键值对rehash到ht[1],此时程序将rehashidx属性的值为-1, 表示rehash操作全部完成。将ht[1]设置为ht[0],并在ht[1]新创建一个空白的哈希表,为下一次rehash准备
总结: 在渐进式 rehash执行期间,新添加到字典的键值对 一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,这一措施保证了ht[0]包含的键值对数量只减不增,并随着rehash操作的执行最终变成空表。
1.5 字典API (主要操作API)
函数 |
作用 |
dictCreate |
创建一个新的字典 |
dictAdd |
将给定的“键值对”添加到字典里面 |
dictReplace |
将给定的“键值对”添加到字典里面,如果键已经存在于字典,那么用新值取代原有的值 |
dictFetechValue |
返回给定的键的值 |
dictGetRandomkey |
从字典中随机返回一个“键值对” |
dictDelete |
从字典中删除给定键所对应的“键值对” |
dictRelease |
释放给定字典,以及字典中包含的所有“键值对” |
最后字典总结:
(1) 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。
(2) Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个在rehash时使用。
(3) 当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。
(4) 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个“键值对”会连接成一个单向链表。
(5) 在对哈希表扩展或收缩操作时,程序需要将现有哈希表包含的所有“键值对”rehash到新哈希表里面,并且这个rehash过程并不是一次性完成的,而是渐近式完成。
redis 系列6 数据结构之字典(下)的更多相关文章
-
redis 系列5 数据结构之字典(上)
一. 概述 字典又称符号表(symbol table),关联数组(associative array), 映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构.在字典中, ...
-
Redis系列二 - 数据结构
前言 redis作为我们开发的一大神器,我们接触肯定不会少,但是很多同学也许只会存储String类型的值,这是非常不合理的.在这里,将带大家认识Redis的5中数据结构. 1.问:Redis有那些数据 ...
-
redis 系列7 数据结构之跳跃表
一.概述 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树(关系型数据库的索引就是平衡树 ...
-
Redis 的底层数据结构(字典)
字典相对于数组,链表来说,是一种较高层次的数据结构,像我们的汉语字典一样,可以通过拼音或偏旁唯一确定一个汉字,在程序里我们管每一个映射关系叫做一个键值对,很多个键值对放在一起就构成了我们的字典结构. ...
-
redis 系列8 数据结构之整数集合
一.概述 整数集合(intset)是集合键的底层实现之一, 当一个集合只包含整数值元素,并且这个集合元素数量不多时, Redis就会使用整数集合作为集合键的底层实现.下面创建一个只包含5个元素的集合键 ...
-
redis 系列4 数据结构之链表
一. 概述 链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可能通过增删节点来灵活地调整链表的长度.作为一种数据结构,在C语言中并没有内置的这种数据结构.所以Redis构建了自己的链表实现 ...
-
redis 系列3 数据结构之简单动态字符串 SDS
一. SDS概述 Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默 ...
-
【目录】redis 系列篇
随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...
-
redis 系列14 有序集合对象
一. 有序集合概述 Redis 有序集合对象和集合对象一样也是string类型元素的集合,且不允许重复的成员.不同的是每个元素都会关联一个double类型的分数.redis正是通过分数来为集合中的成员 ...
随机推荐
-
SQL Server中中数据行批量插入脚本的存储实现
看到博友SQL Server MVP桦仔的一篇博文“将表里的数据批量生成INSERT语句的存储过程的实现”.我仔细看来博文中的两个存储代码,自我感觉两个都不太满意,都是生成的单行模式的插入,数 ...
-
ubuntu用终端卸载软件
我们需要知道我们要卸载的软件的名称,sudo apt-get autoremove --purge 之后输入软件名称,可以先输入前缀之后按tab,会自动补全. 现在不要急着回车,我们来讲解一下这个命令 ...
-
HDU5593 ZYB's Tree 树形DP +分治
感觉其实就是树分治,一次BC的题,感觉这次题目质量比较高,仅代表蒟蒻的看法 一次DFS获取每个点到子树的距离不大于K的点的个数, 然后一遍BFS获取从每个点父亲不大于K的的个数,层层扩展,还是想说 其 ...
-
c++, 虚基派生 : 共同基类产生的二义性的解决办法
虚基派生 //虚继承 #include <iostream> using namespace std; #include <string> //---------------- ...
-
Effective C++ 24,25
24.在函数重载和设定參数缺省值间要谨慎选择. 获得一种类型的数据的最小值或最大值,对于c中,一般使用在<linits.h>中定义的各种宏如INT_MIN 来进行表示,可是这样无法进行泛型 ...
-
转换器4:手写PHP转Python编译器,语法解析部分
写完词法部分,又有很多杂事,周末终于有空来实现伟大的语法解析部分了. 撸完代码之后发现,程序太短了,不算上状态机,才186行(含注释),关键代码不到100行.运行调试过后,发现还行.居然可以解析One ...
-
jQuery 核心函数 (十一)
函数 描述 jQuery() 接受一个字符串,其中包含了用于匹配元素集合的 CSS 选择器. jQuery.noConflict() 运行这个函数将变量 $ 的控制权让渡给第一个实现它的那个库.
-
Servlet(八):ServletContext对象和ServletConfig对象
ServletContext 对象:问题: Request 解决了一次请求内的数据共享问题,session 解决了用户不同请求的数据共享问题,那么不同的用户的数据共享该怎么办呢?解决: 使用 Serv ...
-
Tomcat web.xml配置参数详解
Apache Tomcat Configuration Reference - The Context Containerhttps://tomcat.apache.org/tomcat-5.5-do ...
-
Android Retrofit2.1.0设置编码格式GBK
设置接口如下: public interface IHttpService { /** * 获取userId * @param params * @return */ @FormUrlEncoded ...