rehash过程
步骤
2) 然后将旧哈希表的所有元素都迁移到新哈希表去(rehash)
{
// 当 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); }
}
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和平摊操作
以下是 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 号哈希表。
哈希表的大小
我们知道哈希表最初的大小是由 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
rehash过程的更多相关文章
- Redis的字典(dict)rehash过程源代码解析
Redis的内存存储结构是个大的字典存储,也就是我们通常说的哈希表.Redis小到能够存储几万记录的CACHE,大到能够存储几千万甚至上亿的记录(看内存而定),这充分说明Redis作为缓冲的强大.Re ...
- redis的rehash过程
在扩容和收缩的时候,如果哈希字典中有很多元素,一次性将这些键全部rehash到ht[1]的话,可能会导致服务器在一段时间内停止服务.所以,采用渐进式rehash的方式,详细步骤如下: 为ht[1]分配 ...
- 关于Redis的启动过程
一.简介 Redis的启动也就是main函数的执行,程序的入口在redis.c中,启动流程: 1. 初始化默认服务器配置,如果是sentinel模式还需进行额外的配置 2. 修改配置文件或配置选项,这 ...
- 美团针对Redis Rehash机制的探索和实践
背景 Squirrel(松鼠)是美团技术团队基于Redis Cluster打造的缓存系统.经过不断的迭代研发,目前已形成一整套自动化运维体系,涵盖一键运维集群.细粒度的监控.支持自动扩缩容以及热点Ke ...
- redis渐进式 rehash
转载(http://redisbook.com/preview/dict/incremental_rehashing.html) 上一节说过, 扩展或收缩哈希表需要将 ht[0] 里面的所有键值对 r ...
- 《闲扯Redis八》Redis字典的哈希表执行Rehash过程分析
一.前言 随着操作的不断执行, 哈希表保存的键值对会逐渐地增多或者减少, 为了让哈希表的负载因子(load factor)维持在一个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要 ...
- redis字典快速映射+hash釜底抽薪+渐进式rehash | redis为什么那么快
前言 相信你一定使用过新华字典吧!小时候不会读的字都是通过字典去查找的.在Redis中也存在相同功能叫做字典又称为符号表!是一种保存键值对的抽象数据结构 本篇仍然定位在[redis前传]系列中,因为本 ...
- erlang 分布式数据库Mnesia 实现及应用
先推荐一篇:mnesia源码分析(yufeng) - linear hash ETS/DETS/mnesia 都使用了linear hash算法 http://en.wikipedia.org ...
- HashMap实现原理分析(转)
文章转自:http://blog.csdn.net/vking_wang/article/details/14166593 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但 ...
随机推荐
- Json模块的详细介绍(序列化)
什么叫序列化——将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化. 比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给? 现在我们能想到的方法就是存在文件里,然 ...
- Django中间件,信号,缓存
中间件 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 在django项 ...
- Python获取主机名
import socket print socket.gethostname()
- brew和brew cask安装
brew 是从下载源码解压然后 ./configure && make install ,同时会包含相关依存库.并自动配置好各种环境变量,而且易于卸载. 这个对程序员来说简直是福音,简 ...
- 内网IP&外网IP/NAT
内网的计算机以NAT(网络地址转换)协议,通过一个公共的网关访问Internet.内网的计算机可向Internet上的其他 计算机发送连接请求,但Internet上其他的计算机无法向内网的计算机发送连 ...
- bacula 备份恢复
一.数据恢复: 在bacula服务器执行: /opt/bacula/etc/ bconsole #进入交互窗口 *restore #输入restore恢复命令 Automatically select ...
- $.queue() 与 $.dequeue() -- 队列
JQuery 运用队列为动画模块服务,但好像它应该有更多用处,我觉得的,那试试就知道咯. 简单的来讲,它就是形成队列和出列, 也就因此可以进行很有规律的回调和延时了呀(暂停感觉有难度),当然这就是后面 ...
- 如何单用户模式破解root密码&救援模式破解root密码
学了几天Linux,终于到了装逼时刻,看看如何破译别人的root密码,哈哈哈哈..... 单用户模式破解root密码 重置Centos 7 Root密码的方式 step1 - 在启动grub菜单,选择 ...
- Hadoop单机搭建
单机Hadoop搭建 1.下载hadoop-2.7.3.tar.gz http://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-2.7.3/h ...
- cocos2dx打飞机项目笔记一:项目结构介绍
最近在学习cocos2dx引擎,版本是2.1.3,开发环境是win7 + vs2010,模仿微信打飞机游戏,开发中参考了 csdn 偶尔e网事 的系列文章:http://blog.csdn.net/c ...