(精华)2020年8月27日 数据结构与算法解析 Redis有序集合zset实现原理(跳表)

Redis有序集合中的元素的编码可以是 ziplist 或者 skiplist。ziplist和skiplist编码选择的标准在于Redis里的元素的数量以及元素成员的长度。当满足以下2个条件时,元素编码为ziplist:

  1. 有序集合保存的元素数量小于128个
  2. 有序集合保存的所有元素成员的长度小于64字节

ziplist:

ziplist编码的有序集合对象使用压缩列表作为底层实现。每个集合使用2个紧挨在一起的压缩列表节点来保存,第一个保存元素的成员,第二个保存元素的分值。压缩列表内的集合按分值从小到大排序,分值较小的元素被放置在靠近表头的位置,分值较大的元素在靠近表尾的位置。

skiplist:

跳跃表是一种有序的数据结构,它通过在每个节点上维持多个指向其它节点的指针来达到快速访问的目的。跳跃表在插入、删除和查找操作上的平均复杂度为O(logN),最坏为O(N),可以和红黑树相媲美,但是在实现起来,比红黑树简单很多。在Redis中的zset就是使用了跳跃表的实现有序集合。
具有如下性质:

  1. 由很多层结构组成;
  2. 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
  3. 最底层的链表包含了所有的元素;
  4. 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
  5. 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
    (精华)2020年8月27日 数据结构与算法解析 Redis有序集合zset实现原理(跳表)_第1张图片
    如上图所示,有一个链表保存着若干有序的数字,如果我们想找到 <16,23,25,31>这几个数字的话,简单的方法就是遍历一遍,时间复杂度为O(N)。数据量小的话还好,但是数据量一旦十分庞大的话就非常耗时了。这个时候就是需要我们去优化的了,找一个线性的顺序表,我们一般使用二分法查找,时间复杂度为O(LogN)。但是链表不适用二分法去查找(链表没有线性表那种连续下标,无法确定中间位置)。于是跳表产生了,它是一种随机化的数据结构,类似二叉搜索树,我们把一些节点提取出来作为索引。如下:>这几个数字的话,简单的方法就是遍历一遍,时间复杂度为O(N)。数据量小的话还好,但是数据量一旦十分庞大的话就非常耗时了。这个时候就是需要我们去优化的了,找一个线性的顺序表,我们一般使用二分法查找,时间复杂度为O(LogN)。但是链表不适用二分法去查找(链表没有线性表那种连续下标,无法确定中间位置)。于是跳表产生了,它是一种随机化的数据结构,类似二叉搜索树,我们把一些节点提取出来作为索引。如下:
    (精华)2020年8月27日 数据结构与算法解析 Redis有序集合zset实现原理(跳表)_第2张图片
    我们提取了<13,19,25,34>作为一级索引,这样可以减少比较的次数。同样的,有一级索引就可以建立二级索引,如下,在海量的数据情况下这种方式无疑会大大减少查询的时间O(logN)
    (精华)2020年8月27日 数据结构与算法解析 Redis有序集合zset实现原理(跳表)_第3张图片
    Redis中跳跃表的结构
    在上面简单了解了跳跃表的基本含义及实现思想。在redis中,跳跃表的定义在 server.h 中,包含2个结构体,分别表示跳跃表节点的zskiplistNode和跳跃表zskilist。zskiplistNode 结构用于表示跳跃表节点,zskiplist结构用来保存跳跃表节点的相关信息。源码如下:
typedef struct zskiplistNode {
     
    //节点对应成员的对象,一般是SDS
    robj *obj;
    //分数,跳跃表的顺序按照分值进行排列
    double score;
    //存储后继节点的指针
    struct zskiplistNode *backward;
    // 层级
    struct zskiplistLevel {
     
        // 存储前驱节点的指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;


typedef struct zskiplist {
     
    // 跳跃表的表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    // 跳跃表中节点的数量
    unsigned long length;
    // 表中层数最大的节点层数
    int level;
} zskiplist;

level[] 是跳跃表中非常重要的属性,在初始化一个跳跃表节点的时候会为其随机生成一个层级大小,这个大小通常在1-32之间(不需要太大,假设极端平均的情况下2^32已经是很大了42亿+),作为level数组的大小。数组中的每个元素包含一个指向其它节点的前进指针,通过它来加速访问其它节点。如下图所示,这个层级是随机产生的,所以每个节点拥有的层级是不一定的,但是至少是1级。假设链表 <13,16,19,34>
在这里插入图片描述
它的跳跃表结构可能如下(注意这里说的是可能,因为每个节点的层级Level是随机的):
(精华)2020年8月27日 数据结构与算法解析 Redis有序集合zset实现原理(跳表)_第4张图片

你可能感兴趣的:(#,数据结构篇,数据结构)