Redis之SkipList数据结构
0.前言
Redis中有序集合zset需要使用skiplist作为存储数据结构, 关于skiplist数据结构描述可以查询wiki, 本文主要介绍Redis实现的skiplist的细节.
1.数据结构定义
typedef struct zskiplistNode {
/*成员object对象*/
robj *obj;
/*分数字段依赖此值对skiplist进行排序*/
double score;
/*插入层中指向上一个元素level数组*/
struct zskiplistNode *backward;
struct zskiplistLevel {
/*每层中指向下一个元素指针*/
struct zskiplistNode *forward;
/*距离下一个元素之间元素数量, 即forward指向的元素*/
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
/*跳跃表头节点和尾节点*/
struct zskiplistNode *header, *tail;
/*跳跃表中元素个数*/
unsigned long length;
/*跳跃表当前最大层数*/
int level;
} zskiplist;
2.创建跳跃表
创建跳跃表过程比较简单, 初始化zskiplist数据结构, 跳跃表默认最大层数32层, 跳跃表是按score进行升序排列.
/*创建跳跃表*/
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
zsl = zmalloc(sizeof(*zsl));
zsl->level = 1;
zsl->length = 0;
/*初始化创建一个头节点, 初始化节点信息*/
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = 0;
}
zsl->header->backward = NULL;
zsl->tail = NULL;
return zsl;
}
/*创建一个跳跃表节点*/
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;
}
3.添加元素
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;
/*从头节点开始搜索, 一层一层向下搜索, 直到直到最后一层, update数组中保存着每层应该插入的位置*/
for (i = zsl->level-1; i >= 0; i--) {
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
/*记录每层距离头部位置的距离*/
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
/* 随机一个层数, 如果随机的层数是新的层数, 则需要给update数组中新的层数赋值*/
level = zslRandomLevel();
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
/*新的一层上一个指针肯定是header*/
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
/*创建新的节点插入到update数组对应的层*/
x = zslCreateNode(level,score,obj);
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
/*
header update[i] x update[i]->forward
|-----------|-----------|-----------|-----------|-----------|-----------|
|<---update[i].span---->|
|<-------rank[i]------->|
|<-------------------rank[0]------------------->|
更新update数组中span值和新插入元素span值, rank[0]存储的是x元素距离头部的距离, rank[i]存储的是update[i]距离头部的距离, 上面给出了示意图
*/
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* level可能小zsl->level, 无变动的元素span依次增加1*/
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
/*上一个元素level数组, 重新赋值*/
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
/*下一个元素为空,则表示x为尾部元素*/
zsl->tail = x;
zsl->length++;
return x;
}
4.获取排名
排名其实就是元素在skiplist中排列的序号, 获取排名需要给出分数和成员member, 通过score查找, 匹配member成员, 时间复杂度log(N). 由于skiplist是升序排列的,因此函数返回的rank是score按升序排列的rank, 如果想获取降序rank应该是(length-rank).
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
zskiplistNode *x;
unsigned long rank = 0;
int i;
x = zsl->header;
/*循环遍历并累加每层的span值, 获取总的排名*/
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,o) <= 0))) {
rank += x->level[i].span;
x = x->level[i].forward;
}
/* 判断成员是否相等 */
if (x->obj && equalStringObjects(x->obj,o)) {
/*升序排列的排名*/
return rank;
}
}
return 0;
}
5.根据排名查找元素
/*通过排名查找元素, rank是从1开始, rank是升序排列的rank值*/
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
zskiplistNode *x;
unsigned long traversed = 0;
int i;
/*遍历每一层,并记录排名, 与待查rank比较, 相等则找到, 找不到则返回NULL*/
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
{
traversed += x->level[i].span;
x = x->level[i].forward;
}
/*找到直接返回*/
if (traversed == rank) {
return x;
}
}
return NULL;
}
6.删除元素
删除元素需要精确匹配到分数和member
/*删除一个元素*/
int zslDelete(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i;
x = zsl->header;
for (i = zsl->level-1; i >= 0; 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) < 0)))
x = x->level[i].forward;
update[i] = x;
}
/* 由于score值可能相等, 因此需要精确匹配score和obj值 */
x = x->level[0].forward;
if (x && score == x->score && equalStringObjects(x->obj,obj)) {
zslDeleteNode(zsl, x, update);
zslFreeNode(x);
return 1;
}
return 0; /* not found */
}
/* 具体进行删除元素所在节点*/
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
/*删除元素需要更新update元素的span值*/
for (i = 0; i < zsl->level; i++) {
if (update[i]->level[i].forward == x) {
update[i]->level[i].span += x->level[i].span - 1;
update[i]->level[i].forward = x->level[i].forward;
} else {
update[i]->level[i].span -= 1;
}
}
if (x->level[0].forward) {
/*非尾部元素则需要重置backforward指针*/
x->level[0].forward->backward = x->backward;
} else {
/*删除x可能是最后一个元素, 需要重置尾部指针*/
zsl->tail = x->backward;
}
/*删除元素位于最上层, 并且仅有此一个元素, 删除之后,需要降低跳跃表层数*/
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
zsl->length--;
}
Redis之SkipList数据结构的更多相关文章
- Redis(二)--- Redis的底层数据结构
1.Redis的数据结构 Redis 的底层数据结构包含简单的动态字符串(SDS).链表.字典.压缩列表.整数集合等等:五大数据类型(数据对象)都是由一种或几种数结构构成. 在命令行中可以使用 OBJ ...
- 【Redis】内部数据结构自顶向下梳理
本博客将顺着自顶向下的思路梳理一下Redis的数据结构体系,从数据库到对象体系,再到底层数据结构.我将基于我的一个项目的代码来进行介绍:daredis.该项目中,使用Java实现了Redis中所有的数 ...
- Redis 5种数据结构使用及注意事项
1优缺点 非常非常的快,有测评说比Memcached还快(当大家都是单CPU的时候),而且是无短板的快,读写都一般的快,所有API都差不多快,也没有MySQL Cluster.MongoDB那样更新同 ...
- redis 五种数据结构详解(string,list,set,zset,hash)
redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...
- (2)redis的基本数据结构是动态数组
redis的基本数据结构是动态数组 一.c语言动态数组 先看下一般的动态数组结构 struct MyData { int nLen; ]; }; 这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针, ...
- 2.Redis五种数据结构
2.Redis五种数据结构2.1 预备2.1.1 全局命令2.1.2 数据结构和内部编码2.1.3 单线程架构2.2 字符串2.2.1 命令2.2.2 内部编码2.2.3 典型使用场景2.3 哈希2. ...
- Redis指令与数据结构(二)
0.Redis目录结构 1)Redis介绍及部署在CentOS7上(一) 2)Redis指令与数据结构(二) 3)Redis客户端连接以及持久化数据(三) 4)Redis高可用之主从复制实践(四) 5 ...
- redis 五种数据结构详解(string,list,set,zset,hash),各种问题综合
redis 五种数据结构详解(string,list,set,zset,hash) https://www.cnblogs.com/sdgf/p/6244937.html redis 与 spring ...
- 【Redis】redis 五种数据结构详解(string,list,set,zset,hash)
redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...
随机推荐
- Spfa+DP【p2149】[SDOI2009]Elaxia的路线
Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间. Elaxia和w**每天都要奔波于宿舍和实验室之间 ...
- 表(Table)
虽然我们已经将不同用途的物品保存在不同的仓库中了,但是在同一个仓库中数据的保存仍然存在问题.比如食品分为熟食.生肉.大米等,如果把他们随意的堆放在一起,就会造成我们无法很容易的对这些食品进行管理,当要 ...
- java.net.MalformedURLException: Protocol not found:
出现java.net.MalformedURLException: Protocol not found: 一般来说是url地址没有写对.没有以http://开头,或者出现空格等格式问题: 要看清楚 ...
- 英尺和米之间的转换 Exercise06_09
/** * @author 冰樱梦 * 时间:2018年下半年 * 题目:英尺和米之间的转换 * */ public class Exercise06_09 { public static void ...
- 点击VS2010调试按钮无反应
使用vs打开网站的方式分为两种. 1) 点击“在浏览器中查看”无反应的话,就点击“浏览方式”,选择一个浏览器进行查看. 2)第二中是直接点击调试按钮,结果浏览器无反应. 以上两种方法不成功的原因都是浏 ...
- iOS项目之企业证书打包和发布
一.打包ipa 个人发布证书和企业发布证书打包 app 大同小异,只是打包时导出选项不同,企业证书打包选择 Save for Enterprise Deployment ,并最终导出 ipa 包.详细 ...
- Maven多模块项目单独编译子模块项目时报错:Failed to execute goal on project/Could not resolve dependencies for project
背景:常规的父子项目搭建的工程,参考:http://www.cnblogs.com/EasonJim/p/6863987.html 解决方法: 1.需要把parent工程,也就是package是pom ...
- 使用GIT进行源码管理 —— VisualStudio官方GIT教程
我之前在文章使用GIT进行源码管理 —— 在VisualStudio中使用GIT中简单的介绍了一下如何使用VS中自带的Git工具,今天发现MSDN上现在也有了非常完整的教程,感兴趣的朋友可以看一下: ...
- SQL Server Wait Types Library
https://www.sqlskills.com/blogs/paul/announcing-the-comprehensive-sql-server-wait-types-and-latch-cl ...
- 图解http读书笔记
以前对HTTP协议一知半解,一直不清楚前端需要对于HTTP了解到什么程度,知道接触的东西多了,对于性能优化.服务端的配合和学习中也渐渐了解到了HTTP基础的重要性,看了一些大神对HTTP书籍的推荐,也 ...