一.概述

  接着上篇继续,这篇把数据结构之字典学习完, 这篇知识点包括:哈希算法,解决键冲突, 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 数据结构之字典(下)的更多相关文章

  1. redis 系列5 数据结构之字典(上)

    一. 概述 字典又称符号表(symbol table),关联数组(associative array), 映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构.在字典中, ...

  2. Redis系列二 - 数据结构

    前言 redis作为我们开发的一大神器,我们接触肯定不会少,但是很多同学也许只会存储String类型的值,这是非常不合理的.在这里,将带大家认识Redis的5中数据结构. 1.问:Redis有那些数据 ...

  3. redis 系列7 数据结构之跳跃表

    一.概述 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树(关系型数据库的索引就是平衡树 ...

  4. Redis 的底层数据结构(字典)

    字典相对于数组,链表来说,是一种较高层次的数据结构,像我们的汉语字典一样,可以通过拼音或偏旁唯一确定一个汉字,在程序里我们管每一个映射关系叫做一个键值对,很多个键值对放在一起就构成了我们的字典结构. ...

  5. redis 系列8 数据结构之整数集合

    一.概述 整数集合(intset)是集合键的底层实现之一, 当一个集合只包含整数值元素,并且这个集合元素数量不多时, Redis就会使用整数集合作为集合键的底层实现.下面创建一个只包含5个元素的集合键 ...

  6. redis 系列4 数据结构之链表

    一. 概述 链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可能通过增删节点来灵活地调整链表的长度.作为一种数据结构,在C语言中并没有内置的这种数据结构.所以Redis构建了自己的链表实现 ...

  7. redis 系列3 数据结构之简单动态字符串 SDS

    一.  SDS概述 Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默 ...

  8. 【目录】redis 系列篇

    随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...

  9. redis 系列14 有序集合对象

    一. 有序集合概述 Redis 有序集合对象和集合对象一样也是string类型元素的集合,且不允许重复的成员.不同的是每个元素都会关联一个double类型的分数.redis正是通过分数来为集合中的成员 ...

随机推荐

  1. Paxos协议超级详细解释+简单实例

    转载自:  https://blog.csdn.net/cnh294141800/article/details/53768464 Paxos协议超级详细解释+简单实例   Basic-Paxos算法 ...

  2. notes for python简明学习教程(1)

    print总是以(\n)作为结尾,不换行可以指定一个空 end='' 字符串前面+r, 原始字符串 \ 显示行连接 input()函数以字符串的形式 返回键入的内容 函数参数, 有默认值的形参要放在形 ...

  3. 一种DTO的规划方案

    现在以网页发布的软件非常普遍,叫BS模式.前后端分离也是大趋势,或者说逐渐普及开来,深受前后端程序员的喜爱,我还是习惯以程序员来泛称所有软件制作者.后端需要把数据传送给前端,往往是通过DTO的序列化来 ...

  4. babel分析

    现在都用 ES6 新语法以及 ES7 新特性来写应用了,但是浏览器和相关的环境还不能友好的支持,需要用到 Babel 转码器来转换成 ES5 的代码 相信大家都看到过如下的名词: babel-pres ...

  5. PCB Mark点相关

    1)Mark点用于锡膏印刷和元件贴片时的光学定位.根据Mark点在PCB上的作用,可分为拼板Mark点.单板Mark点.局部Mark点(也称器件级MARK点) 2)拼板的工艺边上和不需拼板的单板上应至 ...

  6. python之lambda函数

    今天复习python,看见一个关于lambda函数的例子,在python中使用lambda在某些时候十分方便,因为不必为了实现某些简单功能而新建一个函数.但是有这么一个lambda实例令我有些疑惑,现 ...

  7. 51nod1007-正整数分组(dp)

    题目 :http://www.51nod.com/Challenge/Problem.html#!#problemId=1007 大意就是给一堆正整数,分成和最接近的两组. 最开始没什么想法,2^n尝 ...

  8. maven build时报错Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test

    [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ ...

  9. 关于Python2 与 Python3 的区别

    Python是一门动态解释性的强类型定义语言. 1.Python2 : ①.臃肿,源代码的重复量很多.   ②.语法不清晰,掺杂着C,php,Java的一些陋习. Python3 : 几乎是重构后的源 ...

  10. window下如何使用文本编辑器(如记事本)创建、编译和执行Java程序

    window下如何使用文本编辑器(如记事本)创建Java源代码文件,并编译执行 第一步:在一个英文目录下创建一个 .text 文件 第二步:编写代码 第三步:保存文件 方法一:选择 文件>另存为 ...