数据结构
// 跳跃列表
struct zsl {
zslnode* header; // 跳跃列表的头结点
int maxLevel; // 跳跃列表当前最高层
map<string, zslnode*> ht; // hash结构所有键值对;???不太理解干啥的
}
// 跳跃列表节点
struct zslnode {
string value; // 节点值
double score; // 节点分数
zslforward*[] forwards; // 向前跳跃节点指针列表
zslnode* backward; // 向后跳跃节点指针
}
// forward节点,封装了跳跃列表节点
struct zslfordward {
zslnode* item;
long span; // 增加跨度参数,用户快速计算节点的rank
}
跳跃列表的结构如下图所示:
- 图中每一个kv即是一个
zslnode
- 每一个
zslnode
有多个forwards
(根绝当前kv的层高,图中-->
),用于指向同层级下一个zslnode
- 每一个
zslnode
仅有一个backward
(图中<--
),用于指向它前一个zslnode
- 每一个
kv header
是整个跳跃列表的头结点,它拥有最大层高,但不是真实数据节点,其节点value为NULL
, score为Double.MIN_VALUE
(最小值)- 跳跃列表的每一次查找都是从
kv header
开始的
- 跳跃列表的每一次查找都是从
- 跳跃列表的
kv
是有序的- 对于
zset
的存储而言,跳表是根据score
排序
- 对于
查找过程
假设现在要查找kv4
这个节点:其查找过程如下:
- 从
kv header
的最高层开始,寻找当前层最后一个比kv4
小的元素- 第四层下一个元素
kv5
已经大于kv4
,所以第四层没有找到
- 第四层下一个元素
- 从
kv header
下降一层,从第三层寻找最后一个比kv4
小的元素- 找到
kv3
,此时跳到kv3
再往后找
- 找到
- 从
kv3
节点开始降一层(到第二层),寻找最后一个比kv4
小的元素- 第二层下一个是
kv5
,比kv4
大,查找失败
- 第二层下一个是
- 从
kv3
再降一层到第一层,此时只需要遍历第一层即可找到kv4
总结下来,跳跃列表的查找过程概括如下:
- 从
kv header
最高层,寻找当前层最后一个比目标元素小的kv temp
- 找不到,则下降一层,继续寻找
- 找到了,则跳到
kv temp
- 从
kv temp
开始,下降一层,继续寻找当前层最后一个比目标元素小的kv
,并重复第一步
插入过程
假设现在要在kv4
和kv5
之间插入一个新元素kv4.5
,其过程大致如下:
- 进行上述查找过程,找到
kv4.5
应该存放的位置 - 根据算法计算出
kv4.5
应该插入的层高level
- 层高level使用随机算法生成,第一层的概率是50%,每高一层,概率下降50%,如level=2概率25%,level=3概率为12.5%
- 解开原
kv4
和kv5
之间的指针连接,分别与kv4.5
各层连接 - 如果插入节点随机的层高大于当前跳跃表的最大层高,则需要更新跳跃表的最大层高
删除过程
- 通过查找过程找到要删除的
kv
- 更新要删除
kv
各层的前向后向指针 - 如果影响最高层高,也要更新跳表最大层高
更新过程
单独跳跃表的更新,即是kv
排序字段的值更新,存在两种情况:
- 排序字段值不影响跳表原有排序,则直接更新kv的排序字段值即可
- 如果影响跳表排序
- 删除原
kv
- 重新插入
kv
- 删除原
单独对跳表的更新理解起来比较抽象,这里还是使用有序集合zset的更新场景来说,假如zset有一个元素<value: golang score: 1.5>
,这时由于某种原因,其值要变更为<value: golang score:2>
(golang的排序分更高了),其过程大致如下:
- 通过
zset
底层的hash
,查找golang
目前对应的socre = 1.5
- 在跳跃列表中,经过查找过程,找到
socre = 1.5
所在的kv
,并要将其改成score = 2
- 查看当前
socre = 1.5
的前后kv
,如果score = 2
未影响其排序,则直接将当前kv
的score改成2即可 - 如果
socre = 2
影响了整体排序,则:- 删除
score = 1.5
的kv
- 插入新的
socre = 2
的kv
- 删除
- 查看当前
数据结构跳跃列表的使用
- 在
排序集合zset
结构中,使用跳跃列表根据socre
排序,存储各集合元素- 同时,zset使用hash记录了
value
到score
的映射
- 同时,zset使用hash记录了