Hash字典

数据结构

凡是Hash字典,首先得有一个数组,这个数组是用来存储<Key, Value>对中的Value。
<Key, Value>对中的Key,是用来计算Value在数组中的下标位置。
一般的做法是,将Key通过特定的Hash算法得到一个整数值,然后将这个整数值与数组长度求余,计算的结果就是这个Value在数组中的位置。
然而当出现Hash冲突,也就是不同的Key得到相同的Hash值的时候,不同的Value将会保存到数组中的同一个位置。那么这个时候数组中就不能单纯的只存储Value了,就要将数组中的元素链表化,变成如下图所示:


image.png

数组中的每个元素,不仅要保存Value,还需要保存Key,以及指向下一个节点的指针。
所以当出现Hash冲突的时候,不同的<Key, Value>对都分配到了同一个数组下标位置,这时候这些不同的<Key, Value>对组成了一个链表,这个数组下标下存储的是一个链表。

Redis中的Hash字典,原理上是类似的,只是在上面包装了一下,加了一些特有的工程信息。
主要包含三部分:字典,Hash表,Hash表节点

Hash表

typedef struct dictht {
  dictEntry **table; //指针数组,用于存储键值对
  unsigned long size; //table数组的大小
  unsigned long sizemask; //掩码 = size - 1
  unsigned long used; //table数组中已存元素个数
} dictht

这里的sizemask的用途是,某个<Kev, Value>对的数组下标值 = Hash(Key) & sizemask。其计算结果等同于Hash(Key)%size,但是因为是位运算,计算效率要比求余运算快得多。

Hash表节点

Hash表中的元素是用dictEntry来封装的。

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

简单点理解就是,dictEntry里面包含了key, value,以及指向下一个dictEntry的next指针。

字典

对hash表进行一层封装,把一些特殊的操作封装到字典里面,比如计算hash值,rehash等。

typedef struct dict{
  dictType *type;  //dictType里面封装了这个字典需要的一些特殊的操作,比如计算key的hash值
  void *privatedata;  //传给dictType里面那些操作所需的参数数据
  dictht ht[2];  //两个hash表,大部分情况下只使用ht[0],在扩容的情况下需要用到另外一个
  long rehashidx;  //rehash的进度,也就是记录目前ht[0]这个table里面哪个数组下标正在rehash
  unsigned long iterators; //当前运行的迭代器数
} dict;

所以Redis Hash字典的整个结构图如下:


image.png
  1. 当往Hash字典中加入<Key, Value>对时,首先在Dict的type中根据Key计算出Hash值;
  2. 然后获取dictht[0].sizemask,通过Hash(key)&dictht[0].sizemask,得到这个<Key, Value>对在dictht[0].table数组中的位置。
  3. 将传入的<Key, Value>封装成一个新的dictEntry存入计算出来的位置。

扩容和缩容

当持续往字典里面添加<Key, Value>对后,字典空间慢慢会不够,这个时候就需要扩容。

  1. 申请一块新空间,大小为原来的两倍,并且往上取2的N次方。比如原来字典里面的数组长度是3,扩容的时候要申请大小为8的空间,因为3的两倍是6,6不是2的N次方,所以往上到8;
  2. 将字典的rehashidx设置为0. 并将ht[1]指向这块扩容后的新空间, ht[1]的size, sizemask, used都相应的更新;
  3. 所有的插入操作都rehash到ht[1]。查找,删除,更新则兼顾ht[0]和ht[1];
  4. 最后把ht[0]里面老的元素都rehash到ht[1],rehashidx设置为正在rehash的ht[0]里面元素的下标值;
  5. 删除ht[0]里面的元素,对调ht[0]和ht[1],将rehashidx设置为-1;
    缩容也是类似的流程。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 上一篇文章详细介绍了Redis的五种常用数据类型,每种数据类型在内存中的数据结构都会因情况而异。接下来,我会用几篇...
    Javar阅读 6,006评论 0 2
  • 哈希表在C++中对应的是map数据结构,但在Redis中称作dict(字典)。Redis只是用了几个简单的结构体和...
    linux大本营阅读 3,931评论 0 0
  • 1、字典,又称符号表(symbol table)、关联数组(associative array)、或者映射(map...
    大飞飞_s8阅读 2,915评论 2 0
  • Redis 之字典和跳表 字典 Redis整个数据库其实就是一个大的字典 以上命令实际上就是设置了数据库字典中一个...
    lxfeng阅读 4,697评论 0 0
  • 一、概念 字典, 又称符号表(symbol table)、关联数组(associative array)或者映射(...
    Vic_is_new_Here阅读 3,359评论 0 0

友情链接更多精彩内容