深入了解Redis【五】内部编码数据结构之字典dict
引言
字典dict相当于java中的HashMap,用来实现redis数据库和redis的hash类型value。
1、数据结构
字典数据结构分为三部分,字典dict、哈希表dictht、节点dictEntry。一个dict包含两个dictht,一个dictht有多个dictEntry,每个dictEntry代表key-val。
1.1、节点dictEntry
1 | /* |
和java中的java.util.HashMap.Node差不多:
1 | static class Node<K,V> implements Map.Entry<K,V> { |
1.2、哈希表dictht
1 | /* This is our hash table structure. Every dictionary has two of this as we |
和java的HashMap差不多
1.3、字典dict
1 | /* |
其中ht[2]是两个dictht哈希表,一般只使用ht[0],ht[1]只有rehash的时候使用。
2、hash算法
作用就是通过计算key的hash值,再通过计算得出索引值,确定此键值对将要放在哈希表数组的哪个位置。
1 | // 计算给定键的哈希值 |
当字典被用作数据库的底层实现或者哈希键的底层实现是使用MurmurHash算法。
这种算法即使输入的键是有规律的,算法仍能给出一个很好的随机分布性,计算速度非常快,使用简单。
反正我是不懂。具体在hyperloglog.c源码中的MurmurHash64A函数。
3、解决键冲突
从哈希节点的数据结构上就能看出其解决键冲突的原理:
1 | // 指向下个哈希表节点,形成链表 |
即链地址法,当有键冲突时,将新的节点放到链表的头节点,这样速度快。
但是如果冲突的过多,还是会有长链表,java中的解决思路是增加红黑树,redis中没有。
4、rehash
当哈希表中的键值对数量太多或者太少时,会通过rehash来操作,为了让hash表的负载因子维持在一个合理范围。
步骤:
- 为ht[1]分配空间
如果是扩张,大小为:目前已使用节点数的两倍:d->ht[0].used*2
如果是收缩,大小为:目前已使用节点数:ht[0].used
最后通过 _dictNextPower(size)计算出真正的大小:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/*
* 哈希表的初始大小
*/
/* Our hash table capability is a power of two */
/*
* 计算第一个大于等于 size 的 2 的 N 次方,用作哈希表的值
*
* T = O(1)
*/
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;
}
} - 转移ht[0]中的所有节点到ht[1]中
即所有节点重新计算哈希值和索引值 - ht[1]与ht[0]转换位置
redis扩容机制和java中的扩容差不多,都是翻倍,老数据表向新数据表转移。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ClawHub的技术分享!