rehash过程_contenthash

rehash过程_contenthash步骤1)首先创建一个比现有哈希表更大的新哈希表(expand)2)然后将旧哈希表的所有元素都迁移到新哈希表去(rehash)dictAdd对字典添加元素的时候,_dictExpandIfNeeded会

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

步骤

1) 首先创建一个比现有哈希表更大的新哈希表(expand)

2) 然后将旧哈希表的所有元素都迁移到新哈希表去(rehash)
 

dictAdd 对字典添加元素的时候, _dictExpandIfNeeded 会一直对 0 号哈希表的使用情况进行检查。
当 rehash 条件被满足的时候,它就会调用 dictExpand 函数,对字典进行扩展。
static int _dict
ExpandIfNeeded(dict *d)  

{  

    // 当 0 号哈希表的已用节点数大于等于它的桶数量,  

    // 且以下两个条件的其中之一被满足时,执行 expand 操作:  

    // 1) dict_can_resize 变量为真,正常 expand  

    // 2) 已用节点数除以桶数量的
比率超过变量 dict_force_resize_ratio ,强制 expand  

    // (目前版本中 dict_force_resize_ratio = 5)  

    if (d->ht[0].used >= d->ht[0].size &&    (dict_can_resize ||  d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))  

    {  return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?   d->ht[0].size : d->ht[0].used)*2);  }  

}
 

将新哈希表赋值给 1 号哈希表,并将字典的 rehashidx 属性从 -1 改为 0:

int dictExpand(dict *d, unsigned long size)  

{  

    // 被省略的代码…  

  

    // 计算哈希表的(真正)大小  

    unsigned long realsize = _dictNextPower(size);  

  

    // 创建新哈希表  

    dictht n;  

    n.size = realsize;  

    n.sizemask = realsize-1;  

    n.table = zcalloc(realsize*sizeof(dictEntry*));  

    n.used = 0;  

  

    // 字典的 0 号哈希表是否已经初始化?  

    // 如果没有的话,我们将新建哈希表作为字典的 0 号哈希表  

    if (d->ht[0].table == NULL) {  

        d->ht[0] = n;  

    } else {  

    // 否则,将新建哈希表作为字典的 1 号哈希表,并将它用于 rehash  

        d->ht[1] = n;  

        d->rehashidx = 0;  

    }  

  

    // 被省略的代码…  

}  
 

 

渐增式rehash和平摊操作

集中式的 rehash 会引起大量的计算工作。

渐增式 rehash将 rehash 操作平摊到dictAddRaw 、dictGetRandomKey 、dictFind 、dictGenericDelete这些函数里面,每当上面这些函数被执行的时候, 
_dictRehashStep 函数就会执行,将 1 个元素从 0 号哈希表 rehash 到 1 号哈希表,这样就避免了集中式的 rehash 。

以下是 dictFind 函数,它是其中一个平摊 rehash 操作的函数:

dictEntry *dictFind(dict *d, const void *key)  

{  

    // 被忽略的代码…  

  

    // 检查字典(的哈希表)能否执行 rehash 操作  

    // 如果可以的话,执行平摊 rehash 操作  

    if (dictIsRehashing(d)) 
_dictRehashStep(d);  

  

    // 被忽略的代码…  

}  

  

其中 dictIsRehashing 就是检查字典的 rehashidx 属性是否不为 -1 :#define dictIsRehashing(ht) ((ht)->rehashidx != -1)  

如果条件成立成立的话, _dictRehashStep 就会被执行,将一个元素从 0 号哈希表转移到 1 号哈希表:

static void _dictRehashStep(dict *d) {    if (d->iterators == 0) dictRehash(d,1);  }  

  

(代码中的 iterators == 0 表示在 rehash 时
不能有迭代器,因为迭代器可能会修改元素,所以不能在有迭代器的情况下进行 rehash 。)

0 号哈希表的元素被逐个逐个地,从 0 号 rehash 到 1 号,最终整个 0 号哈希表被清空,这时 _dictRehashStep 再调用 dictRehash ,被清空的 0 号哈希表就会被删除,然后原来的  1 号哈希表成为新的 0 号哈希表。

当 rehashidx 不等于 -1 ,也即是 dictIsRehashing 为真时,所有新添加的元素都会直接被加到 1 号数据库,这样 0 号哈希表的大小就会只减不增。


哈希表的大小

 我们知道哈希表最初的大小是由 DICT_HT_INITIAL_SIZE 决定的,而当 rehash 开始之后,根据给定的条件,哈希表的大小就会发生变动:

 

static int _dictExpandIfNeeded(dict *d)  

{  

    // 被省略的代码…  

  

    if (d->ht[0].used >= d->ht[0].size &&  

        (dict_can_resize ||  

         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))  

    {  

        return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?  

                                    d->ht[0].size : d->ht[0].used)*2);  

    }  

  

    // 被省略的代码…  

}  

 

可以看到, 
d->ht[0].size 和 d->ht[0].used 两个数之间的较大者乘以 2 ,会作为 size 参数被传入 dictExpand 函数,但是,尽管如此,这个数值仍然还不是哈希表的最终大小,因为在 dictExpand 里面,_dictNextPower 函数会根据传入的 size 参数计算出真正的表大小:

 

int dictExpand(dict *d, unsigned long size)  

{  

    // 被省略的代码…  

  

    // 计算哈希表的(真正)大小  

    unsigned long realsize = _dictNextPower(size);  

  

    // 创建新哈希表  

    dictht n;  

    n.size = realsize;  

    n.sizemask = realsize-1;  

    n.table = zcalloc(realsize*sizeof(dictEntry*));  

    n.used = 0;  

  

    // 被省略的代码…  

}  

 

至于 _dictNextPower 函数,它不断计算 2 的乘幂,直到遇到大于等于 size 参数的乘幂,就返回这个乘幂作为哈希表的大小:

 

static unsigned long _dictNextPower(unsigned long size)  

{  

    unsigned long i = DICT_HT_INITIAL_SIZE;  

  

    if (size >= LONG_MAX) return LONG_MAX;  

    while(1) {  

        if (i >= size)  

            return i;  

        i *= 2;  

    }  

}  

1) 哈希表的大小总是 2 的乘幂(也即是 2^N,此处 N 未知)

2)1 号哈希表的大小总比 0 号哈希表大


最后, 我为 redis 的源码分析项目专门建立了一个 github project ,上面有完整的源码文件,大部分加上了注释(目前只有 dict.c 和 dict.h),如果对代码的完整细节有兴趣,可以到上面去取:  
https://github.com/huangz1990/reading_redis_source

 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/168252.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月3日 上午6:00
下一篇 2022年8月3日 上午6:00


相关推荐

  • docker启动的n8n中自定义url

    docker启动的n8n中自定义url

    2026年3月15日
    2
  • 智能优化算法改进算法 -附代码[通俗易懂]

    智能优化算法改进算法 -附代码[通俗易懂]智能优化算法改进算法摘要:为了方便大家对智能优化算法进行改进,复现多种智能优化改进算法供大家参考。所有代码均根据已经发表的文章,来复现方便大家参考别人的原理,代码会不定时更新。1.文献复现:基于变因子加权学习与邻代维度交叉策略的改进乌鸦算法Matlab代码[1]赵世杰,高雷阜,于冬梅,徒君.基于变因子加权学习与邻代维度交叉策略的改进CSA算法[J].电子学报,2019,47(01):40-48.2.文献复现:自适应t分布变异的缎蓝园丁鸟优化算法Matlab代码[1]韩斐斐,刘升.基于自适

    2022年5月23日
    43
  • macbook安装redis

    macbook安装redis一 官网下载 Redishttps redis io download 选择稳定版本解压 redis 3 0 7 tar gz 拷贝到任意目录执行解压命令 tar zxvfredis 6 2 5 tar gz 二 终端安装编译和安装跳转到解压目录 然后编译 安装 make 安装后执行 makeinstall 基本安装完 配置都采用默认配置 启动 rediscd 到 redis 解压目录下 执行 src redis server 回车即启动 如图所示则启动成功

    2026年3月19日
    1
  • TTL门电路工作原理_TTL门电路和CMOS有什么特点

    TTL门电路工作原理_TTL门电路和CMOS有什么特点CMOS门电路、TTL门电路基础CMOS门电路简介MOS管简介合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与导入导出导入CMOS门电路简介CMOS门电路(ComplementaryMetal-Oxide-Semiconductor)是指利用P沟道

    2025年7月20日
    7
  • CSS面试题总结[通俗易懂]

    CSS面试题总结[通俗易懂]前面的话小柒前面总结了与HTML相关的面试题,这篇文章总结CSS相关面试题。题目(1)盒子模型的理解?盒模型分为两种:标准模式与混杂模式(IE模式)标准盒子模型IE盒子模型一般的我们所说的width、height都是指标准盒子模型下的width(也就是content)。(2)CSS中哪些属性可以同父元素继承?字体系列:font-family,font-siz…

    2022年5月6日
    30
  • UITextView 手势触发 TouchesBegan 函数

    UITextView 手势触发 TouchesBegan 函数前几天做了个手势可以改变文章字体大小的功能。开始,在当前view中添加一个UITextView,然后添加-(void)touchesBegan:(NSSet*)toucheswithEvent:(UIEvent*)event函数,可怎么也触发不了,在网上找了些资料,说得也不是很清楚,今天把它总结下。       首先说原因吧,你把UITextView加载到当前view上,

    2022年7月25日
    10

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号