博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
redis字典结构
阅读量:6968 次
发布时间:2019-06-27

本文共 2504 字,大约阅读时间需要 8 分钟。

目录

前言

大年初五送财神,emmm,希望今年暴富,每年都是这么单纯简单的小愿望,没有一次让我实现的。

年会一个奖都没抽到,emmmm,我很好。

so,还是自己动手,丰衣足食。今天学习redis中的字典。

结构介绍

字典,C语言中没有内置这种数据结构,所以redis自己构建了实现。

hash类型的数据底层就是字典。

哈希表:

typedef struct dictht {    //存放一个数组的地址,数组存放着哈希表节点dictEntry的地址。    dictEntry **table;         //哈希表table的大小,初始化大小为4    unsigned long size;         //用于将哈希值映射到table的位置索引。它的值总是等于(size-1)。    unsigned long sizemask;     //记录哈希表已有的节点(键值对)数量。    unsigned long used;     } dictht;复制代码

哈希节点:

typedef struct dictEntry {  void *key; //key union {        void *val;        uint64_t u64;        int64_t s64;       double d; } v; //value  //指向下一个hash节点,用来解决hash键冲突(collision) struct dictEntry *next;} dictEntry;复制代码

字典:

typedef struct dict {    //指向dictType结构,dictType结构中包含自定义的函数,   //这些函数使得key和value能够存储任何类型的数据。    dictType *type;    //私有数据,保存着dictType结构中函数的参数。    void *privdata;    //两张哈希表。    dictht ht[2];    //rehash的标记,rehashidx==-1,表示没在进行rehash     long rehashidx;    //正在迭代的迭代器数量    int iterators; } dict;复制代码

上面代码整个结构图如下:

注意:这边ht是一个数组,ht[1]为空,是用来进行散列的。

解决冲突

在解决冲突之前,我们先看(k0,v0)为什么会存在下标为1的位置?

这其实是哈希算法,先计算hash值(hash=dict->type->hashFunction(key)),再计算索引值(index=hash&dict->ht[x].sizemask。

那如果再有一个(k2,v2),他的索引值也是下标为1,那就会出现两个值在同一位置的情况。这就是冲突啦。

redis的哈希表采用链地址法来解决键冲突,上面的整个结构图中的哈希节点dictEntry有一个next指针,他是指向下一个节点的。

最新的节点添加到链表的表头位置,这样是为了速度考虑。

重新散列

随着操作的不断进行,哈希表保存的键值对会逐渐的增多或减少,为让哈希表的负载因子(used/size)保持在一个合理的范围内,哈希表会进行扩展和收缩。

简单来说,比如我们现在有10个空间,但是我数据量有30个,这已经平均每个空间都有链表,且链表长度为3。如果极限考虑,这30个数据都在同一节点,那链表长度太长,查询,更新,删除都慢(这里不说新增,是因为每次新增的节点都在表头,与长度无关)。这效率贼慢啊。我们是不是要扩展空间。

再比如我们现在有10个空间,数据量只有1个,这是不是太浪费空间了。我们是不是要收缩空间,等数据量大的时候,我们再扩展嘛。

那扩展和收缩的条件是什么呢?

首先是扩展,没有执行bgsave命令时,负载因子大于等于1;执行bgsave命令时,负载因子大于等于4。

这边重点说明下区分bgsave命令的原因。因为在执行bgsave命令时,需要创建子进程,所以要提高负载因子,避免在子进程执行期间进行扩展,避免不必要的内存写入操作,最大限度的节约内存。

其次是收缩,负载因子小于0.1。

扩展和收缩的步骤如下:

1.确定ht[1]的分配空间。(在重新散列之前,数据都是放在ht[0]中的,ht[1]为空。)

扩展:第一个大于等于ht[0].used*2的

收缩:第一个大于等于ht[0].used的

2.将ht[0]的键值重新散列到ht[1]中。

3.将ht[1]改为ht[0],ht[1]新建一个空白哈希。

渐进式散列

扩展和收缩都需要将ht[0]里面的所有键值对散列到ht[1]中,但是这个动作并不是一次性完成的,而是分多次,渐进式完成的。

这么做的原因在于,如果ht[0]如果只保存了四个键值对,那么服务器可以在瞬间完成,但是如果里面保存的是四百万,四千万的键值对,那么一次性将这些键值对全部散列到ht[1]中,这个计算量还是很庞大的。

因此,为了避免rehash对服务器性能造成影响,服务器不是一次性将ht[0]里面的所有键值对全部散列到ht[1]中,而是分多次,渐进式慢慢的散列。

步骤如下:

1.为ht[1]分配空间。

2.在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。

3.rehash过程中,逐渐将rehashidx加1。

4.rehash结束,将reshidx属性的值设为-1,表示rehash工作已完成。

注意:

如果在重新散列的过程中,还有对该hash的操作,就要分情况啦。

1.如果是新增操作,就将数据添加到ht[1]中。

2.如果是查询,更新,删除等操作,就会ht[0],ht[1]都要查,因为并不知道这条数据现在在哪个数组里面。

这样可以做到ht[0]只增不减,直到整个操作完成。

恩恩,到这里就结束啦,明天见(虽然偶也不知道明天能不能更)。

长按下图二维码,即刻关注【学习Java的小姐姐】 领取超多学习资料哦!

转载地址:http://fcpsl.baihongyu.com/

你可能感兴趣的文章
简单理解倒排索引
查看>>
SpringAop在实际项目中的使用案例
查看>>
哪个对象才是锁?
查看>>
this关键字
查看>>
Python中字符串和datetime
查看>>
ng-Cordova插件之fileTransfer的使用
查看>>
基于struts1.框架的异常处理方案
查看>>
浅谈 Qt 内存管理
查看>>
【Qt】Qt之密码框不可选中、复制、粘贴、无右键菜单等【转】
查看>>
Flume中关于HDFS的sink配置
查看>>
Idea 社区版开发指南-1
查看>>
date命令转换unix时间戳
查看>>
/usr/lib目录属性更改引发的蝴蝶效应
查看>>
比禅道好用的项目管理 项目管理工具Redmine 各功能测试
查看>>
org.apache.commons 常用工具类
查看>>
TabHost的使用
查看>>
OpenGL超级宝典笔记——颜色
查看>>
shell 命令学习
查看>>
身份证号码怎么玩
查看>>
Android UI生成随机颜色
查看>>