携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情
哈希表
hash对象的底层实现之一是压缩列表(后来被listpack替换),还有一个就是哈希表。
哈希表结构设计
dictht
- dictEntry **table 哈希表数组
- size 大小
- sizemask 大小掩码,勇于计算索引
- used 已有的节点数量
dictEntry **table是一个指针,指向哈希表节点dictEntry
哈希表节点结构设计
dictEntry
- key
- v 可以是指针也可以是值,为了节省空间
- *next 指向下一个形成链表,用链式哈希解决哈希冲突
优点和缺陷
优点:
- key独一无二
- O(1)复杂度查询,通过哈希函数确定位置,哈希实际上是个数组, 通过索引值能快速查询到数据
缺陷:
- 哈希冲突,索引值一样的情况下,redis采用链式哈希的方式解决问题
哈希冲突
哈希表实际上是一个数组,每个元素就是一个哈希桶
当key经过哈希函数计算后得到哈希值,再%哈希表大小取模得到元素位置,也就是哈希桶的位置
哈希冲突就是不同的key经过哈希函数和取模运算被放到了相同的哈希桶
链式哈希
每个哈希表节点dictEntry都有一个next指针,勇于指向下一个节点,被分配到同一个哈希桶的节点可以用单链表连接起来。
随着链表长度增加,复制度就是O(n),耗时会越来越大。
解决方案就是rehash扩展
rehash
dictht表示哈希表,但在实际使用中,使用的是dict结构体,里面定义了两个哈希表
dict:
- dictht ht[2]
表2刚开始并没有被分配空间
rehash的操作过程如下:
- 给表2 分配空间,一般是哈希表1大两倍
- 数据迁移到表2
- 释放表1,表2设置为表1,创建空白表为表2
但是如果,表1数据非常大,迁移就会很慢,阻塞造成性能问题。
渐进式rehash
渐进式rehash的过程:
- 表2分配空间
- rehash期间,每次哈希表CRUD,就会额外把表1索引上的元素迁移到表2
- 请求数量多了以后,最终完成迁移。完成rehash
另外,渐进式rehash过程中,查找,如果表1找不到,就会去找表2
新增直接加到表2
表1 只会减少
rehash触发条件
- 负载因子大于等于1,如果没有执行RDB或者AOF持久化就会rehash,也就是bgsave和bgrewiteaof
- 如果大于等于5,强制执行
整数集合
set的底层实现之一,如果set只包含整数值元素,数量不大就用整数集实现
整数集合设计
inset:
- encoding 编码方式
- length 元素数量
- contents[] 保存元素的数组
即使contents被申明为int8_t类型数组,但是不包吃该类型元素,真正类型取决于enconding
- encoding INTSET_ENC_INT16 ,int16_t类型数组
不同类型的数组,大小不同
整数集合的升级操作
如果加入的新元素的类型比集合现有元素长时,需要升级。,扩展contents数组空间,然后再加进去。
在原来数组上进行,encoding为多少就是间隔多少。
在原来的基础上扩容,如果原来为int16,现在升级为32,比如原来有3个元素,新增一个。需要扩容的大小就是432 - 316
扩容后,然后按原来的间隔分割元素,将原来的元素有序放入正确位置
好处:不是直接使用大内存,这样能节省内存资源
但不支持降级操作。删除大元素后不会降级。