半岛bandao体育(中国)官方网站

阿里的面试官问:简要谈谈您对Redis中哈希表的理解

日期:2024-06-21 19:18 / 作者:zoc7RcITctunhMtq7EzA
[[433454]] 回顾哈希表

哈希表是一种用于存储数据的结构,它有多种名称(键值对、字典、符号表、映射、关联数组)。 哈希表中,每个键都对应着唯一的值,形成一一对应的映射关系。也就是说,每个键key对应着一个值value。使用哈希表这种数据结构,可以在O(1)的时间复杂度下通过键key来获取相应的数值。因为C语言本身没有内置哈希表这种数据结构,所以Redis自行实现了一个Hash表。哈希冲突是哈希表最重要的问题之一,需要找出解决办法。在这种情况下,通过哈希函数计算后,发现这两个条目会被存储在同一个位置。针对这种状况,需要采取进一步行动。解决哈希碰撞的方法大家应该熟悉我写的数据结构与算法基本知识,还记得处理哈希碰撞的技巧吗。使用开放地址法(x,n,x)执行线性探查。

这种策略的主要思想是:一旦遇到冲突,就应该将该项目推迟至后面。让我们看一个具体的例子吧。1. 使用哈希算法找到新键值对应的位置时,如果该位置已经被占据,就应当使用开放地址法。\n2. 因此,应当按照顺序寻找下一个可用位置,使用开放地址法。\n3. 若顺延位置依然被占据,就继续顺延至下一个可用位置,使用开放地址法。\n4. 继续往后寻找可用位置,直到成功存储新值为止,这种方法被称为链地址法(拉链法)。\nRedis 数据库采用的存储冲突解决方法即为链地址法(拉链法)。请看以下的示例。应该将新的键值对存储在二号位置,因为此时该位置已经包含了一个键值对。所以,直接将链表挂载在键值对1的下面。

链式法

同样适用于新的键值对,将其以链表的形式挂在键值对2的下方。

重新处理

在谈论重新处理之前,首先要介绍一个概念:装填因子。让我们来了解一下负载因子的概念吧:负载因子 = 散列表内元素个数 / 散列表的长度。如果负载因子很高,意味着哈希冲突的概率很大,会严重影响查找效率。若负载因子较低,则意味着哈希表可能占用了过多空间,大部分空间未被利用。为了保持负载因子在适当范围内,程序必须对哈希表进行扩展或收缩。由于空间的扩大或缩小,导致之前的键在原表中的存储位置,在新表中可能不同,因此需要重新计算。重新计算哈希值,并将原表元素转移到新表元素的过程称为rehash。无论是在Java中的HashMap、ConcurrentHashMap,还是今天介绍的Redis哈希表,都会有重新哈希的过程。Redis中的哈希表数据结构,让我们来看一下Redis的哈希表逻辑设计结构。Redis的哈希表主要由三个结构构成:dictht。这个表达了一个哈希表

dictEntry。哈希表的一个条目,可以被视为一个键值对

dict。Redis生成的哈希表结构,可供外部调用。包含两个dictht

typedef struct dictht {      dictEntry **table; // 哈希表数组(存储哈希表项)     unsigned long size; // 哈希表大小      unsigned long sizemask; // 哈希表掩码     unsigned long used;// 已使用的哈希表大小 } dictht; 

简单解释一下各个项。

阿里的面试官问:简要谈谈您对Redis中哈希表的理解

table:指针数组,存放哈希表项 size:哈希表的大小,相信这个不需要多解释 sizemask: 掩码。

桶:指向哈希表项的数组 大小:哈希表的容量,应该不需要进一步解释 大小掩码:这个值设计得很巧妙。比如Redis的长度为3,你想访问第5个元素,如果按照以前的方法,肯定会访问到超出Redis哈希表范围的地址空间。因此,Redis 规定在访问元素时,需要先对索引和大小进行按位与操作,截断超出 Redis 长度的部分,以防止内存安全问题的发生。哈希表已经使用的空间大小。不解释。他讲解了哈希表。让我们看看哈希项

typedef struct dictEntry {      void *key;      union {          void *val;          uint64_t u64;          int64_t s64;          double d;      } v;      struct dictEntry *next; } dictEntry; 

我们了解到,Redis使用拉链法来解决哈希碰撞的问题。因此,Redis中的哈希表项包括一个next指针,可用于指向下一个元素。通过这个指针,可以访问多个拥有相同哈希值的键值对。由此可知,Redis的哈希表中的每个元素都带有一个next指针,用于指向下一个元素。通过该指针,可以访问具有相同哈希值的多个键值对。现在我们继续来了解一下dict结构。

typedef 结构体 字典 {     字典类型 dictType *type;     void *私有数据;     dictht 哈希表[2];     整型 rehash索引; } 字典; 

大家一定很好奇,为什么一个字典要搞两个哈希表呢?当然也会有一些不太好奇的朋友,不过面试官不好奇也是无可奈何的情况啊。答案出炉了,这两个哈希表是用来进行再哈希的。在什么情况下需要重新进行哈希呢?当负载因子大于或等于1时,如果redis没有在进行后台备份,则执行。既然CPU空闲着也是闲着,那么当负载因子大于或等于5时,就执行Redis的后台备份。CPU正在执行备份操作,如果我们的表非常拥挤,我们就需要对其进行一些调整。等CPU空闲下来后,我们再稍微调整一下有点拥挤的rehash。现在我们来看一下如果需要进行rehash的情况下,需要执行的步骤:分配空间给ht[1]。空间的分配取决于ht[0]中的特定参数。将ht[0]中存储的键值对重新计算哈希值和索引值,然后将其赋值到ht[1]相应的位置上。完成赋值后,需要释放ht[0]所占用的空间,然后将ht[0]指向ht[1]当前的地址。 ht[1] 指向一个空表。由于在一定时间内执行步骤二的计算方式会占用过多资源,因此Redis提出了渐进式rehash的方法。用通俗易懂的说法,就是过去是一次性地搬运,而现在变成了分批搬运BOB半岛入口。在分批搬运的过程中,很可能会收到各种各样的请求。对于写请求,也就是在redis中添加新的键值对时,redis会直接将数据存储在ht[1]表中。在查询请求时,也就是查找特定键对应的值时,Redis会先在ht[0]表中搜索,如果找不到,就会在ht[1]表中搜索。在处理更新请求时,Redis 首先会在 ht[0] 表中进行查找,若查找不成功,就会在 ht[1] 表中进行更新。Redis在处理删除请求时,会先在ht[0]表中进行查找,若未匹配到则会在ht[1]表中执行删除操作。 请查阅BOB半岛新版

https://www.cnblogs.com/tekkaman/p/5141936.html

https://blog.csdn.net/yangbodong22011/article/details/78467583

Redis的设计与实现

 

阿里的面试官问:简要谈谈您对Redis中哈希表的理解

Redis源码解析与实战

 

阿里的面试官问:简要谈谈您对Redis中哈希表的理解

BOB半岛APP

BOB半岛官方


BOB半岛APP BOB半岛入口 BOB半岛娱乐