一套打通Redis(3)--sorted set的底层实现跳跃表、整数集合

一、跳跃表

首选,思考,那种数据结构在插入、删除、查找等操作上性能比较优呢?

我们知道,数组的优点是支持随机取值,但是如果是插入数据的话,首先需要查找到数据,考虑使用二分查找那么时间复杂度就是O(logn),找到数据以后进行插入因为数组的空间是连续的需要将数值进行移动,这个过程的时间复杂度就是O(n),所以总时间复杂度是O(n)

链表的优点是插入很快速,对于插入操作时间复杂度是O(1),但是因为链表不支持二分查找那么定位到一个元素的位置需要的时间复杂度就是O(n),所以总体时间复杂度就是O(n)

考虑,平衡二叉树,平衡二叉树的插入、查找都很高效但是每一次都需要花费比较大的精力去维护平衡算法,而跳跃表是一种使用使用时间换空间的结构

下面看跳跃表的具体实现:

跳跃表是基于链表实现的,单区别在于含有很多层,每个节点的每层都有指向表尾方向最近一个节点的指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O0dHc4Q4-1619507033832)(https://c1.lmlphp.com/user/master/2019/08/01/17fedab97780fbeca36f52a1692e0cc9.png)]

  • 图中是含有四层结构的跳跃表,bw是指向前一个节点的指针,每个节点只有一个,删除时方便。
  • 每个节点都含有一个或多个指向表尾最近一个节点的指针。
  • 最底层(L1层)包含所有的节点。

下面看其查找的过程

跳跃表的优秀表现在于查询功能,以上图中查找值为90的节点为例,如果在链表中继续要进行顺序查找,需要进行9步才能查询到,而在上图的跳跃表中则需要6步就能完成,具体步骤如下:

(1)由最高层L4层开始查询,L4层当前节点值10,小于90,则取当前点的下一个节点120,大于90,这时降层到L3层查找;即查找值处于当前节点的值和当前节点下一节点的值之间时,降层查询。

(2)当前节点为10,L3层,取其下一节点40比较,小于90,进行下一步。

(3)节点40,取其下一节点80比较,小于90,进行下一步。

(4)节点80,取其下一节点120比较,大于90,此时将80设置为当前节点,并在当前节点上降层,进行下一步。

(5)当前节点80,L2层,取下一节点100比较,大于90,当前节点不变,直接降层,进行下一步。

(6)当前节点80,L1层,取下一节点90比较,等于90,结束,返回。

一共经过6步,虽然说只节省了3步,但这是在我们查询的是90节点,如果我们查找的是120节点,那么只需要1步便可以查询到了,所以其平均时间复杂度为O(logN);跳跃表在节点数量少的情况下,性能的提升不明显,当节点在1w以上时,性能提升将会非常明显。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLYYhg6q-1619507033836)(https://c1.lmlphp.com/user/master/2019/08/01/feeaef242f8cff0bd47b3f9c1db426cb.png)]

摘抄至https://www.lmlphp.com/user/7152/article/item/354954/

总结:其实跳跃表是在链表的基础上,因为链表本身是不支持二分查找的,而跳跃表则强行增加多个层,使其可以使用二分查找,使其查找效率变高,而且不需要像红黑树一样维护整个结构的有序要进行寻旋转,非常适合在并发的环境下更新数据的场景,而Redis就使用了跳跃表

二、整数集合

整数集合是集合键的底层实现之一,当一个集合只包含整数值的元素,并这个元素的数据不多时,Redis就会使用整数集合作为集合键的底层实现

整数集合是Redis用于保存整数值的集合的抽象数据结构,可以保存类型为int16,int32,int64的整数集,并且保证集合中不出现重复元素

typedef struct intset {
     
  uint32_t encoding; // 决定contents保存的真正类型
  uint32_t length;
  int8_t contents[]; // 各项从小到大排序
} inset;

每当需要将一个元素添加到整数集合里面,并且新元素的类型比整数集合现有的所有类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面,总共分为三步:

  1. 根据新元素的类型,扩展底层数组的空间大小,并为新元素分配空间。
  2. 将底层数组现有元素转换成与新元素相同的类型,并放置在正确的位置上(从后向前遍历)。放置过程中,维持底层数组的有序性质不变。
  3. 将新元素添加到底层数组里。

因为每次升级都可能对所有元素进行类型转换,所以复杂度为O(N)

使用升级真正策略,有以下几个好处:

  • 提升灵活性,C语言是静态类型语言,为了避免类型错误,通常不会将两种不同的数据类型的值存在同一个数据结构里面,但是整数集合采用了升级策略,这就可以将int16,int32,int64存入集合里面,提高灵活性
  • 节约内存,采用遇到一个需要升级的类型才进行升级,这样可以尽量节省内存

可以将int16,int32,int64存入集合里面,提高灵活性

  • 节约内存,采用遇到一个需要升级的类型才进行升级,这样可以尽量节省内存

还有一点就是Redis是不支持降级的,也就是编码中的类型长度只会变长而不会变短

你可能感兴趣的:(Redis,redis)