一.概述

  接着上篇继续,这篇把数据结构之字典学习完, 这篇知识点包括:哈希算法,解决键冲突, 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. HDU 2204 Eddy's 爱好 (容斥原理)

    <题目链接> 题目大意: Ignatius 喜欢收集蝴蝶标本和邮票,但是Eddy的爱好很特别,他对数字比较感兴趣,他曾经一度沉迷于素数,而现在他对于一些新的特殊数比较有兴趣. 这些特殊数是 ...

  2. hcna(华为)_Telnet篇

    Telnet提供了一个交互式操作界面,允许终端远程登录到任何可以充当 Telnet服务器的设备.Telnet用户可以像通过Console口本地登录一样对 设备进行操作.远端Telnet服务器和终端之间 ...

  3. Android中Adapter类的使用 “Adapter”

    Adapter用来把数据绑定到扩展了AdapterView类的视图组(例如:ListView或Gallery).Adapter负责创建代表所绑定父视图中的底层数据的子视图. 可以创建自己的Adapte ...

  4. Linux服务器限制ssh登录,查看登录日志

    网络上的服务器很容易受到攻击,最惨的就是被人登录并拿到root权限.有几个简单的防御措施: 1. 修改ssh服务的默认端口 ssh服务的默认端口是22,一般的恶意用户也往往扫描或尝试连接22端口.所以 ...

  5. VB输出数据到EXCEL

    Private Sub Command1_Click() Dim i As Long Dim j As Long , ) As Long Dim xlApp, WS, WB Set xlApp = C ...

  6. [转]XModem协议

    出处:XModem协议 XModem协议介绍:XModem是一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即 ...

  7. LeetCode 80 Remove Duplicates from Sorted Array II [Array/auto] <c++>

    LeetCode 80 Remove Duplicates from Sorted Array II [Array/auto] <c++> 给出排序好的一维数组,如果一个元素重复出现的次数 ...

  8. 整理SpringMVC

    Spring Web MVC核心架构图: 核心架构图流程如下: 1.首先用户发送请求------->DispatcherServlet(前端控制器),前端控制器收到请求后自己不进行处理,而是委托 ...

  9. Java Fileupload

    fileupload FileUpload 是 Apache commons下面的一个子项目,用来实现Java环境下面的文件上传功能,与常见的SmartUpload齐名. 组件 1.FileUpLoa ...

  10. spring boot 集成 Redis

    前提:你已经安装了Redis 1.创建一个spring boot 工程 2.pom 引入依赖:spring-boot-starter-data-redis <dependency> < ...