跳跃表是一种随机化的数据结构,在查找、插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单。

和链表、字典等数据结构被广泛地应用在Redis内部不同,Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群结点中用作内部数据结构。除此之外,跳跃表在Redis里面没有其他用途。

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
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;

obj是该结点的成员对象指针,score是该对象的分值,是一个浮点数,跳跃表中的所有结点,都是根据score从小到大来排序的。

同一个跳跃表中,各个结点保存的成员对象必须是唯一的,但是多个结点保存的分值却可以是相同的:分值相同的结点将按照成员对象的字典顺序从小到大进行排序。

level数组是一个柔性数组成员,它可以包含多个元素,每个元素都包含一个层指针(level[i].forward),指向该结点在本层的后继结点。该指针用于从表头向表尾方向访问结点。可以通过这些层指针来加快访问结点的速度。

每次创建一个新跳跃表结点的时候,程序都根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是该结点包含的层数。

Redis中的跳跃表,与普通跳跃表的区别之一,就是包含了层跨度(level[i].span)的概念。

层跨度用于记录本层当前结点到下一个 结点之间的距离,举个例子,如下图的跳跃表:节点1在第0层的下一个节点是2,span=1;在第1层的下一个节点是3,span=2;在第2层的下一个节点是4,span=3;所以计算的节点在每层的跨度以跨越第0层上的节点数量为准。如果新节点的level要比整个表的level低,导致update[i].level[i]在本层的下一个节点为null的,循环结束后对此类节点的span++,所以此类节点的span代表的是到第0层最后一个节点的距离

插入节点的算法如图,先找到在每层的插入位置,并保存在update数组中,同时将头节点到该位置的跨度累加,保存在rank数组中。最后计算随机高度,在每层插入节点。

zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->obj = obj;
return zn;
} zskiplist *zslCreate(void) {
int j;
zskiplist *zsl; zsl = zmalloc(sizeof(*zsl));
zsl->level = ;
zsl->length = ;
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,,NULL);
for (j = ; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = ;
}
zsl->header->backward = NULL;
zsl->tail = NULL;
return zsl;
}/* Returns a random level for the new skiplist node we are going to create.
* The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
* (both inclusive), with a powerlaw-alike distribution where higher
* levels are less likely to be returned. */
int zslRandomLevel(void) {
int level = ;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += ;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
} zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level; redisAssert(!isnan(score));
x = zsl->header;
for (i = zsl->level-; i >= ; i--) {
/* store rank that is crossed to reach the insert position */
rank[i] = i == (zsl->level-) ? : rank[i+];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < ))) {
rank[i] += x->level[i].span;//累加本层从头节点到插入位置节点的跨度综合
x = x->level[i].forward;
}
update[i] = x;//得到每层的插入位置节点
}
/* we assume the key is not already inside, since we allow duplicated
* scores, and the re-insertion of score and redis object should never
* happen since the caller of zslInsert() should test in the hash table
* if the element is already inside or not. */
level = zslRandomLevel();
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = ;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
x = zslCreateNode(level,score,obj);
for (i = ; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x; /* update span covered by update[i] as x is inserted here */
x->level[i].span = update[i]->level[i].span - (rank[] - rank[i]);//update[i]->level[i].span - 0层和i层的update[i]之间的距离
update[i]->level[i].span = (rank[] - rank[i]) + ;//新增一个节点在后面,所以跨度加一
} /* increment span for untouched levels */
for (i = level; i < zsl->level; i++) {//如果新节点的层数小于表的level,将updata[i]->level[i]的span++
update[i]->level[i].span++;
} x->backward = (update[] == zsl->header) ? NULL : update[];
if (x->level[].forward)
x->level[].forward->backward = x;
else
zsl->tail = x;
zsl->length++;
return x;
}

redis源码分析之数据结构:跳跃表的更多相关文章

  1. Redis源码分析-底层数据结构盘点

    前段时间翻看了Redis的源代码(C语言版本,Git地址:https://github.com/antirez/redis), 过了一遍Redis数据结构,包括SDS.ADList.dict.ints ...

  2. Redis源码解析:05跳跃表

    一:基本概念 跳跃表是一种随机化的数据结构,在查找.插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单.跳跃表的定义如下 ...

  3. redis源码分析之数据结构--dictionary

    本文不讲hash算法,而主要是分析redis中的dict数据结构的特性--分步rehash. 首先看下数据结构:dict代表数据字典,每个数据字典有两个哈希表dictht,哈希表采用链式存储. typ ...

  4. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  5. redis源码分析之事务Transaction(下)

    接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...

  6. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  7. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

  8. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  9. Redis源码分析(dict)

    源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...

随机推荐

  1. 两种Tensorflow模型保存的方法

    在Tensorflow中,有两种保存模型的方法:一种是Checkpoint,另一种是Protobuf,也就是PB格式: 一. Checkpoint方法: 1.保存时使用方法: tf.train.Sav ...

  2. PAT Basic 1004 成绩排名 (20 分)

    读入 n(>)名学生的姓名.学号.成绩,分别输出成绩最高和成绩最低学生的姓名和学号. 输入格式: 每个测试输入包含 1 个测试用例,格式为 第 1 行:正整数 n 第 2 行:第 1 个学生的姓 ...

  3. SQL注入--盲注及报错注入

    盲注查询 盲注其实就是没有回显,不能直观地得到结果来调整注入数据,只能通过其他方式来得到是否注入成功,主要是利用了一些数据库内置函数来达到的 布尔盲注 布尔很明显Ture跟Fales,也就是说它只会根 ...

  4. python出现Non-ASCII character '\xe6' in file statistics.py on line 19, but no encoding declared错误

    可按照错误建议网址查看http://www.python.org/peps/pep-0263.html 发现是因为Python在默认状态下不支持源文件中的编码所致.解决方案有如下三种: 一.在文件头部 ...

  5. POJ - 2774 Long Long Message (后缀数组/后缀自动机模板题)

    后缀数组: #include<cstdio> #include<algorithm> #include<cstring> #include<vector> ...

  6. centos7安装es

    #安装java1.8rpm -ivh jdk-8u191-linux-x64.rpm #解压estar -zxvf elasticsearch-6.4.0.tar.gz -C /usr #修改es限制 ...

  7. MyEclipse使用教程:使用工作集组织工作区

    [MyEclipse CI 2019.4.0安装包下载] 工作集允许您通过过滤掉不关注的项目来组织项目视图.激活工作集时,只有分配给它的项目才会显示在项目视图中. 如果您的视图中有大量项目,这将非常有 ...

  8. jar包部署在linux上后浏览器访问不到的问题

    1.首先保证程序是正常运行的 2.linux的防火墙是否关闭 3.可能是iptables里面需要设置白名单 可编辑/etc/sysconfig/iptables文件加入应用端口的白名单 修改后执行sy ...

  9. Lettuce连接池——解决“MXBean already registered with name org.apache.commons.pool2:type=GenericObjectPool,name=pool”

    LettuceConfig: package com.youdao.outfox.interflow.config; import io.lettuce.core.support.Connection ...

  10. mysql 递归查找所有子节点

    select dept_id from ( select t1.dept_id,t1.parent_id, if(find_in_set(parent_id, @pids) > 0, @pids ...