当前位置:网站首页>redis 源码分析 跳表实现
redis 源码分析 跳表实现
2022-07-17 05:08:00 【HjasnJH】
redis中为什么要使用跳表的数据结构?
reids 的zset有序集合的实现,需要兼顾方便的查找,插入和删除,如果使用链表,查找效率位O(N),这对redis不可接受,使用红黑树,则实现比较麻烦,所以redis使用了一种新的结构,跳表。

上图是一个理想跳表
如果你现在要查找21,你只需要从最高的第2层开始,找到18,跳到第1层,找到24,跳到第0层,找到21,如果数据量大,这将达到了二分查找的效果。
从上面理想跳表,我们可以思考下如果定义一个跳表的数据结构,应该具备什么?
如果是一个跳表的节点:比如1这个节点
跳表的粗略的数据结构
struct MySkipListLevel
{
TMySkipListNode* m_pNext; // 下一个节点
int m_nSpan; // 当前层跳离下一节点的间隔
};
struct TMySkipListNode
{
void* m_pData; // 数据
MySkipListLevel m_L[]; // 每一层
TMySkipListNode* m_pPrev; // 查找到24,要往回走
};
struct TMySkipList
{
TMySkipListNode* m_pHeadNode;
int m_nLeves;
};reids跳表
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode* backward; // 后退指针, 只能指向当前节点最底层的前一个节点,
struct zskiplistLevel {
struct zskiplistNode *forward; // 指向后一个节点
unsigned long span; // forward指向的节点与本节点之间的元素个数
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;其中,多了尾部指针tail,length,一会看看有什么用,数据部分的差异忽略不计。
redis实现的跳跃表

1、创建跳跃表节点
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
zskiplistNode *zn =
zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->ele = ele;
return zn;
}2、创建跳跃表并创建头节点
新跳跃表的默认层高是多少?
int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}从代码可以看出,假设小于(ZSKIPLIST_P * 0xFFFF) 的概率为p
大于(ZSKIPLIST_P * 0xFFFF)的概率为1-p
则层数为n,对应的(p^(n-1)) * (1-p)
其数学期望为:E=1/(1-p),当p为0.25时,则对应的E = 1.33
创建跳跃表,头节点不存储信息,层数64,无数据。
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;
}3、插入节点
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
x = zsl->header;
// 从最高层往底层访问
// rank[i]是第i层对应的可插入新节点的前一个节点的经过的span(也就是头节点到插入节点的前一个节点的间隔),最高层为0
// update[i] 是第i层可以插入节点的前一个节点
for (i = zsl->level-1; i >= 0; i--) {
/* store rank that is crossed to reach the insert position */
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 &&
sdscmp(x->level[i].forward->ele,ele) < 0)))
{
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
/* we assume the element is not already inside, since we allow duplicated
* scores, reinserting the same element should never happen since the
* caller of zslInsert() should test in the hash table if the element is
* already inside or not. */
// 假定不会重复插入,新建一个高度为level的节点,如果比原来的调表的高度高,需要更新对应的rank和update
level = zslRandomLevel();
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
// 创建节点,更新节点的前向节点和后向节点信息
x = zslCreateNode(level,score,ele);
for (i = 0; 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[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* increment span for untouched levels */
// 插入新的节点后,对应的高出来的那几层需要更新span
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
// 设置后向节点上数值
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
zsl->tail = x;
// 更新跳表长度
zsl->length++;
return x;
}插入跳表的节点,1、关键在于如果低于层数,则高于当前跳表层数的要赋值对应的层的数据。2、维持好插入节点的前后节点。3、维持好插入节点后整个跳表的其他节点,长度,层高的影响。
4、删除节点:
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
// 下一个节点就是被删除节点,则加上被删除节点的间距-1,不是被删除节点,直接-1
// 这里假定,被删除的节点一定存在跳表中
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) {
x->level[0].forward->backward = x->backward;
} else {
zsl->tail = x->backward; // 更新尾节点
}
// 跳表高度大于1,最高层没有下一个节点,层数减1,调整高度
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
// 更新长度
zsl->length--;
}5、删除跳跃表:
void zslFree(zskiplist *zsl) {
zskiplistNode *node = zsl->header->level[0].forward, *next;
zfree(zsl->header);
while(node) {
next = node->level[0].forward;
zslFreeNode(node);
node = next;
}
zfree(zsl);
}
从最底层遍历,然后往后遍历,释放他的所有的节点。
跳表redis的应用:
redis中,zset有两种实现,跳表和压缩列表
当元素个数小于128时,使用压缩列表
元素个数大于128时,使用跳表
所以zset当满足条件时,会转换为跳表实现,而且不会转回来。
边栏推荐
- Using JS to realize the second level menu of anjuke and the full version (demonstration of precautions and problem points)
- Excel导入长数据末尾变000
- <script>标签内容详解
- [ES6] explain in detail the common objects and other methods of set and array (full version)
- Two JS methods of rolling wheel loading and modal box dragging
- 路由器loopback口实验
- js 原生对象加属性
- es6新增-函数部分
- Es6 真实案例解构(多维数组对象)全新案例:
- Case summary of rotation chart moving speed (constant speed, slow motion)
猜你喜欢

Shell script configures root to login to other hosts without secret
![[AI] action recognition using simple neural network -- Based on coco key points](/img/67/cd6be6e070fb5d4d44ee043ebd7fac.png)
[AI] action recognition using simple neural network -- Based on coco key points

Easypoi之excel模板导出

Solve the problem of inconsistent prediction effect between text detection training model and information model based on paddleocr

How to deal with the mismatch between subtitle files and video files

Nacos configuration management

Mongo DB aggregate operations and indexes

BUUCTF web WarmUp

Wechat applet obtains the week, morning, noon and evening of month, year and day

mysql 缓存策略和解决方案
随机推荐
轮播图移动速度(匀速,缓动)案例归总
Pat class B 1017: a divided by B
Class object automatic injection attribute operation tool
小程序云开发 上传图片到云存储
LeetCode53. 最大子数组和
OpenDDS的QoS和自定义QoS(校时TimingQosPolicy)
Easypoi excel simple export
PAT乙级1002:写出这个数
【Es6】利用添加数据,筛选并传输至页面等多项功能实现案例
获取数组中对象内部的数值最大与最小值多功能版及点名系统完整版并展示效果
运维安全要了解的二三事
JS native object plus attributes
手把手教你复现Log4j2核弹级漏洞
Applet cloud development form submission and data acquisition in the page
Multifunctional (Implementation) encapsulation function
(精讲)Es6 剩余参数,ES6内置对象,模板字符串内容(详例宝典)及灵活运用项目的实战案例
Cesium geojson数据的添加与移除
小程序云开发表单提交并在页面中获取数据
【Es6】forEach,for...in ,for...of专栏,让你通过项目案例快速分辨各种for语句的使用方式及区别(完整版)内部有详细注释
轮播图的两种方法及自动轮播