【Redis系列4】Redis哈希对象之hashtable(哈希表)和ziplist(压缩列表)实现原理分析

Redis中的key-value是通过dictEntry对象来实现的,而哈希表就是将dictEntry对象进行了再一次的包装得到的,这就是哈希表对象dictht

typedef struct dictht {

dictEntry **table;//哈希表数组

unsigned long size;//哈希表大小

unsigned long sizemask;//掩码大小,用于计算索引值,总是等于size-1

unsigned long used;//哈希表中的已有节点数

} dictht;

PS:table是一个数组,其每个元素都是一个dictEntry对象

字典

字典,又称为符号表(symbol table),关联数组(associative array)或者映射(map),字典的内部嵌套了哈希表dictht对象,下面就是一个字典ht的定义:

typedef struct dict {

dictType *type;//字典类型的一些特定函数

void *privdata;//私有数据,type中的特定函数可能需要用到

dictht ht[2];//哈希表(注意这里有2个哈希表)

long rehashidx; //rehash索引,不在rehash时,值为-1

unsigned long iterators; //正在使用的迭代器数量

} dict;

其中dictType内部定义了一些常用函数,其数据结构定义如下:

typedef struct dictType {

uint64_t (*hashFunction)(const void *key);//计算哈希值函数

void *(*keyDup)(void *privdata, const void *key);//复制键函数

void *(*valDup)(void *privdata, const void *obj);//复制值函数

int (*keyCompare)(void *privdata, const void *key1, const void *key2);//对比键函数

void (*keyDestructor)(void *privdata, void *key);//销毁键函数

void (*valDestructor)(void *privdata, void *obj);//销毁值函数

} dictType;

所以当创建一个哈希对象时,可以得到如下简图(部分属性被省略):

在这里插入图片描述

PS:最后哈希表中的k和v保存的是一个字符串对象。

rehash操作

ht[2]定义了两个哈希表,ht[0]和ht[1]。而Redis在默认情况下使用的是ht[0],不会为ht[1]初始化分配空间。

当设置一个哈希对象时,具体会落到哈希数组(上图中的dictEntry*[3])中的哪个下标,是通过计算哈希值来确定的,如果发生哈希碰撞,那么同一个下标就会有多个dictEntry,从而形成一个链表(最后插入的总是落在链表的最前面),链表越长,性能越差。所以为了保证哈希表的性能,需要在满足以下两个条件中的一个时,对哈希表进行rehash(重新散列)操作:

  • 1、负载因子大于等于1且dict_can_resize设置为1时

  • 2、负载因子大于等于安全阈值(dict_force_resize_ratio=5)时

PS:负载因子=哈希表已使用节点数/哈希表大小(即:h[0].used/h[0].size)。

rehash步骤

扩展哈希和收缩哈希都是通过执行rehash来完成,主要经过以下五步:

  • 1、为字典dict的ht[1]哈希表分配空间,其大小取决于当前哈希表已保存节点数(即:ht[0].used)。

(a)、扩展操作则ht[1]的大小为2n中第一个大于等于ht[0].used * 2属性的值(比如used=3,此时23就是第一个大于used * 2 的值(22<6且23>6))。

(b)、收缩操作则ht[1]大小为2n中第一个大于等于ht[0].used的值。

  • 2、将字典中的属性rehashix的值设置为0,表示正在执行rehash操作。

  • 3、将ht[0]中所有的键值对依次重新计算哈希值,并放到ht[1]数组对应位置,完成一个键值对的rehash之后rehashix的值需要加1。

  • 4、当ht[0]中所有的键值对都迁移到ht[1]之后,释放ht[0],并将ht[1]修改为ht[0],然后再创建一个新的ht[1]数组,为下一次rehash做准备。

  • 5、将字典中的属性rehashix设置为-1,表示rehash已经结束

渐进式rehash

上面介绍的这种方式因为不是一次性全部rehash,而是分多次来慢慢的将ht[0]中的键值对rehash到ht[1]的操作就称之为渐进式rehash。渐进式rehash可以避免了集中式rehash带来的庞大计算量,采用了分而治之的思想。

在渐进式rehash过程中,因为还可能会有新的键值对存进来,此时Redis的做法是新添加的键值对统一放入ht[1]中,这样就确保了ht[0]键值对的数量只会减少

当执行rehash操作时需要执行查询操作,此时会先查询ht[0],查找不到结果再到ht[1]中查询

ziplist


关于ziplist的一些特性,在上一篇讲述列表底层数据结构的时候已经进行过了详细分析(想要详细了解的,可以点击这里)。但是哈希对象中的ziplist和列表对象中ziplist的不同之处在于哈希对象是一个key-value形式,所以其ziplist中也表现为key-value,key和value紧挨在一起:

在这里插入图片描述

ziplist和hashtable的编码转换


当一个哈希对象可以满足以下两个条件时,哈希对象会选择使用ziplist编码来进行存储:

  • 1、哈希对象中的所有键值对总长度(包括键和值)小于64字节(这个阈值可以通过参数hash-max-ziplist-value 来进行控制)。

最后

分享一套我整理的面试干货,这份文档结合了我多年的面试官经验,站在面试官的角度来告诉你,面试官提的那些问题他最想听到你给他的回答是什么,分享出来帮助那些对前途感到迷茫的朋友。

面试经验技巧篇
  • 经验技巧1 如何巧妙地回答面试官的问题
  • 经验技巧2 如何回答技术性的问题
  • 经验技巧3 如何回答非技术性问题
  • 经验技巧4 如何回答快速估算类问题
  • 经验技巧5 如何回答算法设计问题
  • 经验技巧6 如何回答系统设计题
  • 经验技巧7 如何解决求职中的时间冲突问题
  • 经验技巧8 如果面试问题曾经遇见过,是否要告知面试官
  • 经验技巧9 在被企业拒绝后是否可以再申请
  • 经验技巧10 如何应对自己不会回答的问题
  • 经验技巧11 如何应对面试官的“激将法”语言
  • 经验技巧12 如何处理与面试官持不同观点这个问题
  • 经验技巧13 什么是职场暗语

面试真题篇
  • 真题详解1 某知名互联网下载服务提供商软件工程师笔试题
  • 真题详解2 某知名社交平台软件工程师笔试题
  • 真题详解3 某知名安全软件服务提供商软件工程师笔试题
  • 真题详解4 某知名互联网金融企业软件工程师笔试题
  • 真题详解5 某知名搜索引擎提供商软件工程师笔试题
  • 真题详解6 某初创公司软件工程师笔试题
  • 真题详解7 某知名游戏软件开发公司软件工程师笔试题
  • 真题详解8 某知名电子商务公司软件工程师笔试题
  • 真题详解9 某顶级生活消费类网站软件工程师笔试题
  • 真题详解10 某知名门户网站软件工程师笔试题
  • 真题详解11 某知名互联网金融企业软件工程师笔试题
  • 真题详解12 国内某知名网络设备提供商软件工程师笔试题
  • 真题详解13 国内某顶级手机制造商软件工程师笔试题
  • 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题
  • 真题详解15 某著名社交类上市公司软件工程师笔试题
  • 真题详解16 某知名互联网公司软件工程师笔试题
  • 真题详解17 某知名网络安全公司校园招聘技术类笔试题
  • 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题

资料整理不易,点个关注再走吧
某顶级手机制造商软件工程师笔试题

  • 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题
  • 真题详解15 某著名社交类上市公司软件工程师笔试题
  • 真题详解16 某知名互联网公司软件工程师笔试题
  • 真题详解17 某知名网络安全公司校园招聘技术类笔试题
  • 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题

[外链图片转存中…(img-JbXOPrz8-1718870488837)]

资料整理不易,点个关注再走吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值