redis读书笔记之跳跃表

1 zset

zset是redis中的一种有序集合,通过给定的分值以及value在插入的时候就进行排序。
常见的操作:

//插入元素  score用于排序
zadd key score value
//获取区间类的元素
zrange key startindex endindex
//删除元素 
zrem key value1 value2
//获取集合中元素的个数
zcard key
...

zset的底层有两种实现,ziplist和skiplist。本文着重介绍skiplist,即跳跃表。

2 什么是跳跃表

	跳跃表主要是通过建立多层索引的方式来增加查找的效率。那么怎么建立多层索引,又是如何提高查找效率的呢?
	举个例子加深理解:

单链表
在如上图所示的链表,查询节点8,需要从1一直遍历到8,需要查找8次。
我们可以通过在原始链表的基础上,添加索引,如下图所示:
一层跳跃表
查询节点8,可以先通过查找L1层的节点,首先查找1->3->5->7,7的下一个索引是9,下沉到下一层,也就是图中的原始链表,7的下一个节点就是8,一共查找了5次。
同理,我们还可以再增加2层、3层。。。理论上来说,层数越高,查找的效率越高。

3. redis跳跃表的定义和效率

定义:跳跃表是一种有序的数据结构,它是通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。用作Redis有序集合键(zset)的底层实现之一,和集群节点中用作内部数据结构。

效率:平均时间复杂度是O(logN),最坏O(N)的节点查找,还可以通过顺序性来批量处理节点。大部分情况下,跳跃表的效率可以和平衡树相媲美,并且实现比平衡树更为简单。

4. 跳跃表的实现

Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义的,其中,zskiplistNode结构是跳跃表节点,zskiplist结构则是用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等。
举例如下所示:
跳跃表

4.1 zskiplist

zskiplist结构则是用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等。

结构定义如下:

typedef struct zskiplist{
//头指针,指向跳跃表的表头节点
structz skiplistNode *header 
//尾指针,指向跳跃表的表尾节点

structz skiplistNode *tail
//层数,记录跳跃表节点层数最大的层数
int level
//长度,记录跳跃表的长度
unsigned long length
}zskiplist;

4.2 zskiplistNode

跳跃表的节点。

结构定义如下:

typedef struct zskiplistNode{
//层
struct zskiplistLevel{
   //前进指针
   struct zskiplistNode *forward;
   //跨度
   unsigned int span;
}level[ ];
//后退指针
struct zskiplistNode *backward;
//分值
double score;
//成员对象
robj *obj;
}zskiplistNode;

1.层:跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,理论上说,层的数量越多,访问节点的速度就越快。(每次创建一个新的跳跃表节点,程序都根据幂次定律随机生成一个介于1和32之间的值作为level)

幂次定律:越大的数出现的概率越小。

举个例子:
level层数
如图所示:分别为1层,3层和5层的节点。

2.前进指针
每一层都有指向表尾方向的前进指针,用于从表头向表尾方向访问节点。
访问过程如下所示:
遍历跳跃表
3.跨度
层的跨度用于记录两个节点之间的距离:
两个节点之间的跨度越大,相距越远
指向NULL的所有前进指针的跨度为0

跨度主要是用来计算rank的。举个例子:
跨度计算rank
如上图所示,虚线标记了在跳跃表中查找分值为3.0、成员对象为o3的节点时:查找只经过了一层,并且跨度为3,所以目标节点在跳跃表中的排位为3。

4.后退指针
用于从表尾向表头方向访问节点(BW)。不能跳跃,只能指向前一个节点。
访问过程如下图所示:
后退指针访问跳跃表
5.分值和成员
节点的分值(score属性)是一个double类型的浮点数,跳跃表所有的节点都按从小到大的顺序排序(分值相同,则按照成员对象的字典大小进行排序)。
节点的成员对象(obj属性)是一个指针,指向一个字符串对象,而字符串对象则保存着一个sds值。

5 跳跃表的插入和删除

5.1 插入

跳跃表的插入删除比起平衡树的插入来说,显得更加容易实现和简洁。
跳跃表节点的插入和删除主要分为六个步骤:

1.遍历,记录update和rank,找到每一层的前一个节点。
2.生成随机层数。
3.插入新节点。
4.更新未涉及到的层。
5.设置后继指针。
6.更新跳跃表节点的个数。

1.遍历、记录
顾名思义,遍历跳跃表,找到需要插入节点的位置所在,也就是需要找到需要插入位置的前一个节点。(在遍历的时候,由于不知道生成节点的层数大小,所以每一层都有可能插入,需要从最高层查找,一直遍历到最低层位置)。记录每一层的上一个节点和update节点的排名。

2.随机生成层数
上面也说到了,跳跃表在redis中节点的层数是随机生成的,满足幂次定律。
生成随机层数有两种情况:
随机层数大于当前跳跃表记录的层数。更新跳跃表层数。
随机层数低于最大层数,不做更新。

3.插入新节点
找到位置后,单链表插入。

4、5 更新涉及到的层的span值,和后继指针。
6. 更新跳跃表的个数。

例子:
在这里插入图片描述

5.2 删除

同插入原理类似

6 跳跃表的API

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值