Redis基础-跳表

news/2025/2/11 14:43:55/文章来源:https://www.cnblogs.com/json-92/p/18709695

一、跳表原理

 

1.1、什么是跳表

 跳跃表是一种随机化的数据结构,在查找、插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单。跳跃表的定义如下:
        “Skip lists are data structures  that use probabilistic  balancing rather than  strictly  enforced balancing. As a result, the algorithms for insertion and deletion in skip lists  are much simpler and significantly  faster  than  equivalent  algorithms for balanced trees.”
        译文:跳跃表使用概率平衡,而不是强制平衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。 
 

1.2、理想跳表

 
在理想情况下:
  • 最底层元素是一个单链表
  • 从最底层往上,每一层的元素个数是之前一层的1/2
 
 
 
 
 

二、跳表redis实现

 
 
Base:redis version: 5.0.8
 

2.1、redis跳表原理和数据结构

 
 
 

2.2、Redis跳表代码

2.2.1、Redis跳表数据结构

 
 
#跳表节点
typedef struct zskiplistNode {sds ele;double score;struct zskiplistNode *backward;#跳表表示level的数据struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span;} level[];
} zskiplistNode;#跳表 list结构
typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;#zset 结构
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

  

 

 

2.2.2、跳表执行流程

 
#zset 添加/更新操作入口
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {/* Turn options into simple to check vars. */int incr = (*flags & ZADD_INCR) != 0;int nx = (*flags & ZADD_NX) != 0;int xx = (*flags & ZADD_XX) != 0;*flags = 0; /* We'll return our response flags. */double curscore;/* NaN as input is an error regardless of all the other parameters. */if (isnan(score)) {*flags = ZADD_NAN;return 0;}/* Update the sorted set according to its encoding. */if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {unsigned char *eptr;if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {/* NX? Return, same element already exists. */if (nx) {*flags |= ZADD_NOP;return 1;}/* Prepare the score for the increment if needed. */if (incr) {score += curscore;if (isnan(score)) {*flags |= ZADD_NAN;return 0;}if (newscore) *newscore = score;}/* Remove and re-insert when score changed. */if (score != curscore) {zobj->ptr = zzlDelete(zobj->ptr,eptr);zobj->ptr = zzlInsert(zobj->ptr,ele,score);*flags |= ZADD_UPDATED;}return 1;} else if (!xx) {/* Optimize: check if the element is too large or the list* becomes too long *before* executing zzlInsert. */zobj->ptr = zzlInsert(zobj->ptr,ele,score);if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||sdslen(ele) > server.zset_max_ziplist_value)zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);if (newscore) *newscore = score;*flags |= ZADD_ADDED;return 1;} else {*flags |= ZADD_NOP;return 1;}} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {#当前zset编码是 skipListzset *zs = zobj->ptr;zskiplistNode *znode;dictEntry *de;#根据 ele从dict中获取元素,如果获取到直接更新该节点value,如果获取不到就插入de = dictFind(zs->dict,ele);if (de != NULL) {/* NX? Return, same element already exists. */if (nx) {*flags |= ZADD_NOP;return 1;}curscore = *(double*)dictGetVal(de);/* Prepare the score for the increment if needed. */if (incr) {score += curscore;if (isnan(score)) {*flags |= ZADD_NAN;return 0;}if (newscore) *newscore = score;}/* Remove and re-insert when score changes. */if (score != curscore) {//更新znode节点znode = zslUpdateScore(zs->zsl,curscore,ele,score);/* Note that we did not removed the original element from* the hash table representing the sorted set, so we just* update the score. */dictGetVal(de) = &znode->score; /* Update score ptr. */*flags |= ZADD_UPDATED;}return 1;} else if (!xx) {ele = sdsdup(ele);//插入新的节点znode = zslInsert(zs->zsl,score,ele);serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);*flags |= ZADD_ADDED;if (newscore) *newscore = score;return 1;} else {*flags |= ZADD_NOP;return 1;}} else {serverPanic("Unknown sorted set encoding");}return 0; /* Never reached. */
}

  

 
 
 
插入新的节点流程 
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;unsigned int rank[ZSKIPLIST_MAXLEVEL];int i, level;serverAssert(!isnan(score));#初始化遍历初始值#rank 数组收集每层level从header到小于score的最后一个节点的span值#update 数组收集每层level 小于score的最后一个节点x = zsl->header;for (i = zsl->level-1; i >= 0; i--) {/* store rank that is crossed to reach the insert position */#rank[level-]的初始值为0 ,是因为 当前是计算的第一个元素,需要从header.level[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 &&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值,如果当前值大于当前list的level值,需要初始化[当前level,level)之间的level元素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);//从下到上,修改每层小雨score的最后一个元素 ,与当前元素level[i]的链接关系以及span数值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 */#修改每一层小于score的最后一个元素的span值,在这里再执行一次是因为当前节点的level如果小于list的level,则大于当前节点level到list level之间的level的span值还没有被处理,而这些数值只是单纯的增加1for (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;elsezsl->tail = x;#修改当前list 元素个数zsl->length++;return x;
}

  

 
 
 
 
跳表level函数:
#define ZSKIPLIST_P 0.25     
#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
int zslRandomLevel(void) {int level = 1;while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))level += 1;return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

  

redis中规定,level最多只能有64层
 
 

三、跳表java实现

 

3.1、数据结构

 
java实现时去掉了dict结构,主要演示node的插入
 
 
@Data
public class  ZSkipListNode {private String ele;private double score;private ZSkipListNode backward;private ZSkipListLevel[] level;public ZSkipListNode(String ele, double score) {this.ele = ele;this.score = score;this.level = new ZSkipListLevel[ZSkipList.MAX_LEVEL];for(int j = 0;j< ZSkipList.MAX_LEVEL;j++){level[j] = new ZSkipListLevel();}}@Overridepublic String toString() {return "ZSkipListNode{}";}
}@Data
public class ZSkipListLevel {private ZSkipListNode forword;private long span;@Overridepublic String toString() {return "ZSkipListLevel{}";}
}@Data
public class ZSkipList {public static final int MAX_LEVEL = 32;public static final double LEVEL_FACTOR = 0.25;private ZSkipListNode header;private ZSkipListNode tail;private long length;private int level;public ZSkipList() {this.level = 1;this.length = 0;this.header = new ZSkipListNode(null,0);for(int j = 0;j<MAX_LEVEL;j++){this.getHeader().getLevel()[j].setForword(null);this.getHeader().getLevel()[j].setSpan(0);}this.getHeader().setBackward(null);this.setTail(null);}}

  

3.2、insert和delete方法实现
 
@Data
public class ZSkipList {public static final int MAX_LEVEL = 32;public static final double LEVEL_FACTOR = 0.25;private ZSkipListNode header;private ZSkipListNode tail;private long length;private int level;public ZSkipList() {this.level = 1;this.length = 0;this.header = new ZSkipListNode(null,0);for(int j = 0;j<MAX_LEVEL;j++){this.getHeader().getLevel()[j].setForword(null);this.getHeader().getLevel()[j].setSpan(0);}this.getHeader().setBackward(null);this.setTail(null);}public ZSkipListNode insert(double score, String ele) {long[] rank = new long[MAX_LEVEL];ZSkipListNode[] update = new ZSkipListNode[MAX_LEVEL];ZSkipListNode h = this.getHeader();for(int i=getLevel()-1;i>=0;i--){rank[i] = i == getLevel()-1 ? 0 : rank[i+1];while (h.getLevel()[i].getForword()!=null&& (h.getLevel()[i].getForword().getScore()<score|| (h.getLevel()[i].getForword().getScore() == score && valCmp(h.getLevel()[i].getForword().getEle(),ele)<0))){rank[i]+= h.getLevel()[i].getSpan();h = h.getLevel()[i].getForword();}update[i] = h;}int level  = zslRandomLevel();if(level>getLevel()){for(int l = getLevel();l<level;l++){update[l] = getHeader();update[l].getLevel()[l].setSpan(getLength());rank[l]= 0;}}ZSkipListNode cur = new ZSkipListNode(ele,score);for(int i = 0;i<level;i++){cur.getLevel()[i].setForword(update[i].getLevel()[i].getForword());update[i].getLevel()[i].setForword(cur);cur.getLevel()[i].setSpan(update[i].getLevel()[i].getSpan()-(rank[0]-rank[i]));update[i].getLevel()[i].setSpan(rank[0]-rank[i]+1);}for (int i = level;i<this.getLevel()-1; i++){update[i].getLevel()[i].setSpan(update[i].getLevel()[i].getSpan()+1);}cur.setBackward(update[0] == getHeader() ? null  : update[0]);if(cur.getLevel()[0].getForword()!=null){cur.getLevel()[0].getForword().setBackward(cur);}else {this.setTail(cur);}if(level>getLevel()){this.setLevel(level);}this.setLength(this.getLength()+1);return cur;}/*** 删除成功 返回1 ,删除失败,或者没有找到返回0* 删除元素* 并修改剩下的level中的 span信息* @return*/public int delete(ZSkipListNode node,ZSkipListNode[] update){for(int i=0;i<getLevel();i++){ZSkipListLevel level =update[i].getLevel()[i];if(level.getForword() == node){level.setSpan(level.getSpan()+node.getLevel()[i].getSpan()-1);level.setForword(node.getLevel()[i].getForword());}else {level.setSpan(level.getSpan()-1);}}/*** 修改返回指针*/if(node.getLevel()[0].getForword()==null){this.setTail(node.getBackward());}else {node.getLevel()[0].getForword().setBackward(node.getBackward());}/*** 修改level*/while (getLevel()>0 && getHeader().getLevel()[getLevel()-1].getForword()==null){setLevel(getLevel()-1);}setLength(getLength()-1);return 1;}/*** int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node)* @param score* @param ele* @return*/public int deleteWithScore(double score,String ele){ZSkipListNode[] update = new ZSkipListNode[MAX_LEVEL];ZSkipListNode h = getHeader();ZSkipListNode x = null;for(int i= getLevel()-1;i>=0;i--){while (h.getLevel()[i].getForword() != null&& (h.getLevel()[i].getForword().getScore() < score|| (h.getLevel()[i].getForword().getScore() == score && valCmp(h.getLevel()[i].getForword().getEle(), ele) < 0))) {h = h.getLevel()[i].getForword();}update[i]=h;}x = h.getLevel()[0].getForword();if(x!=null && x.getScore() == score && valCmp(x.getEle(),ele) == 0){return delete(x,update);}return 0;}private int zslRandomLevel() {int level = 1;Random random = new Random();while(random.nextInt(20)<20*LEVEL_FACTOR){level+=1;}return level<MAX_LEVEL ? level : MAX_LEVEL;}public int valCmp(String t, String target) {return t.compareTo(target);}public static void main(String[] args) {ZSkipList list = new ZSkipList();Map<String,Integer>  eleMap = new HashMap<>();Random random = new Random();int max = 0;for (int i = 0;i<10;i++){int score =1+random.nextInt(20);max +=score;String ele = "a"+i;list.insert(max,ele);eleMap.put(ele,max);}printZsl(list);/*** 删除元素测试*/for (Map.Entry<String,Integer> entry : eleMap.entrySet()){list.deleteWithScore(entry.getValue(),entry.getKey());System.out.printf("delete,ele:"+entry.getKey()+",score:"+entry.getValue());printZsl(list);}}public static void printZsl(ZSkipList list){ZSkipListNode header = list.getHeader();System.out.println("---------------------------------------");for(int i = list.getLevel()-1;i>=0;i--){System.out.println("level = "+i);ZSkipListNode h = header.getLevel()[i].getForword();while (h!=null){System.out.printf("[node:"+h.getEle()+":"+h.getScore()+",span:"+h.getLevel()[i].getSpan()+"]-");h = h.getLevel()[i].getForword();}System.out.println();}System.out.println("---------------------------------------");}@Overridepublic String toString() {return "ZSkipList{";}
}

  

 
 
 
 
 
 
 
 
 
 
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/882094.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux Android 下的内存注入手段

ptrace_inject_mem long ptrace(enum __ptrace_request request, pid_t pid,void *addr, void *data);请求 (Request) 值 说明PTRACE_TRACEME 0 使调用进程变为被跟踪进程。在子进程调用 ptrace(PTRACE_TRACEME, ...) 后,父进程可以使用 PTRACE_ATTACH 进行跟踪。PTRACE_PEEKT…

解决 idea 无法创建java8 模版

解决 idea 无法创建java8 模版由于 Spring 官方不再维护 旧的版本了导致的 https://start.spring.io 解决方式很简单 切换到 阿里源就可以了 https://start.aliyun.com/ 项目生成 成功总结 :IDEA 内置的 Spring Initializr 创建 Spring Boot 项目实际上是依赖官方链接功能,官…

高性价比云服务新选择

在数字化转型加速的当下,寻找稳定可靠的云服务成为许多用户的刚需。深度测试,3丰云平台提供的免费云服务器**展现出超出预期的实用性,现将其核心优势整理如下: 一、零门槛云资源获取 通过官网([https://www.sanfengyun.com)的清晰引导,用户可快速完成注册并开通免费云服…

MySQL索引、MVCC、锁问题

一、MySQL索引 1.1.索引简介 索引是一种数据库中的数据对象,它能够提高数据库中的数据检索速度.MySQL支持多种类型的索引,每种类型的索引有其特定的用途和性能特点. MySQL中的索引种类如下:B-Tree索引 数据结构B-Tree 根据叶子结点的存储数据的种类不同分为:聚簇索引(主键索引)…

OpenEuler部署DM8主备复制集群

DM8、主备复制案例说明: 在OpenEuler系统上部署DM8的主备复制架构。 系统环境: [root@node209 dm]# cat /etc/os-release NAME="openEuler" VERSION="20.03 (LTS-SP4)" ID="openEuler" VERSION_ID="20.03" PRETTY_NAME="openEu…

OpenEuler部署DM8主备复制

DM8、主备复制案例说明: 在OpenEuler系统上部署DM8的主备复制架构。 系统环境: [root@node209 dm]# cat /etc/os-release NAME="openEuler" VERSION="20.03 (LTS-SP4)" ID="openEuler" VERSION_ID="20.03" PRETTY_NAME="openEu…

对称密码算法

1. 定义 对称密码算法加密过程与解密过程使用相同的或容易相互推导得出的密钥,即加密和解密两方的密钥是“对称”的 2. 加解密流程用户通过加密算法将明文变换为密文。密文的具体值由密钥和加密算法共同决定。只有掌握了同一个密钥和对应解密算法的用户才可以将密文逆变换为有…

SaaS+AI应用架构:业务场景、智能体、大模型、知识库、传统工具系统

大家好,我是汤师爷~ 在SaaS与AI应用的演进过程中,合理的架构设计至关重要。本节将详细介绍其五个核心层次:业务场景层:发现和确定业务场景 智能体层:构建可复用的智能应用 大模型层:采用最合适的大模型,作为思考推理的核心 知识库:管理企业的核心知识资产 传统工具:传…

50N06-ASEMI吹风筒专用MOS管50N06

50N06-ASEMI吹风筒专用MOS管50N06编辑:ll 50N06-ASEMI吹风筒专用MOS管50N06 型号:50N06 品牌:ASEMI 封装:TO-252 最大漏源电流:50A 漏源击穿电压:60V 批号:最新 RDS(ON)Max:15mΩ 引脚数量:3 沟道类型:N沟道MOS管 芯片尺寸:MIL 漏电流: 恢复时间:ns 芯片材质: …

瑞芯微开发板/主板Android调试串口配置为普通串口方法

本文介绍瑞芯微开发板/主板Android调试串口配置为普通串口方法,不同板型找到对应文件修改,修改的方法相通。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。…

Ubuntu22.04 搭建离线APT源(apt-mirror)

1、应用场景 目前需要在Linux环境下做UE开发,Ubuntu版本使用的是22.04。项目现场是没法连到外网的,所以安装库十分不方便,所以需要搭建一套内网的离线APT源。 2、搭建步骤 2.1 资源拉取 首先需要在联网机器上将资源拉取到本地。 本教程使用使用apt-mirror拉取镜像源,使用ng…

013 Promise对象_Ajax实操

Promise封装Ajax,让网络请求的异步操作变得更简单<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><titl…