前言
这几天在读《Redis 设计与实现》,推荐大家阅读一下。加上也看了很多的博客,在这里总结一下关于Redis跳跃表的笔记
先把关于Redis跳跃表的重点总结一下在这里:
- 跳跃表是有序集合(zset)的底层实现之一
- redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点,表尾节点,长度),而zskiplistNode则用于表示跳跃表节点
- 每个跳跃表节点的层高都是1至32之间的随机数
- 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的
- 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序
GitHub上也有源码–>Redis源码地址
关于跳跃表的位置在t_zset.c里面,对具体实现有兴趣的同学可以去研究一下
一、什么是跳跃表?
前面咱们介绍了单链表和双链表,其实跳跃表也是一种链式的数据存储结构,具体是用一种类似于”索引”的思想,提取出链表中的部分关键节点。
如果是一个单链表的话,他查找数据的时间复杂度为O(n),咱么给单链表添加一级索引 每两个节点提取一个节点到上一级,我们把诌出来的哪一级叫做索引或者索引层,如下图:
我们要是查找值为6的节点,那么路线如下
是不是要比我们从单链表一个一个查找的要快很多。
当然,既然已经提取出了一层关键节点作为索引,那么可以进一步提取索引,提出一层索引的索引。如下:
如果我们要查找值为6的节点,只需要经过1->5这个两个节点就找到了,是不是变得更快了。
跳跃表的层级极限:当同一层只有两个节点的时候, 就达到了跳跃表的层级极限,因为一个节点没有比较的意义
跳跃表是一种随机化的数据结构(通过抛硬币来决定层数)
当大量的新节点通过逐层比较,最终插入到原链表之后,上层的索引节点会渐渐变得不够用。这时候需要从新节点当中选取一部分提到上一层,可是究竟提拔谁,忽略谁?关于这一点,跳跃表的设计者采用了抛硬币的方式。
意思就是说随机决定新节点是否提拔,每次向上提拔一层的几率是50%。
那么为什么要用抛硬币?
- 因为跳跃表删除和添加的节点是不可预测的,很难用一种有效的算法来保证跳表的索引部分始终均匀。
- 随机抛硬币的方法虽然不能保证索引绝对均匀分布,却可以让大体趋于均匀
二、认识Redis的跳跃表的结构
Redis的跳跃表与我们普通的跳跃表区别相差很多
此图来源于《Redis的设计与实现》
使用跳跃表的优势:
- 效率和红黑树以及avl树不相上下
- 原理简单
Redis只在两个地方用到了跳跃表
1.实现有序集合键
2.在集群节点中作用内部数据结构
总结
1.跳跃表 VS 二叉查找树
二叉树的插入,删除,查找也是近似O(logn)的时间复杂度
不过,二叉查找树是有可能出现一种极端的情况,那就是如果插入的数据刚好有序,那么所有的节点会偏向某一边
这种结构会导致二叉查找树效率变为O(n),这会使二叉查找树大打折扣
2.跳跃表 VS 红黑树
红黑树比跳跃表复杂,学习成本偏高
而且红黑树插入,删除节点时,是通过调整结构来保持红黑树的平衡,比起跳跃表直接通过一个随机数来决定跨越几层,在时间复杂度的花销上是要高于跳跃表的。
也不是说红黑树比跳跃表差,在某些情况下,红黑树也是一种很好的选择,具体情况具体分析