准备工作

先在Redis官网下载最新的稳定版本6.2。按照官网给出的安装指南到Linux服务器上安装。

zadd调用过程

redis/src/server.c 文件中定义了所有命令运行要调用的方法。zadd命令运行会调用t_zset.c文件的zaddCommand方法。

struct redisCommand redisCommandTable[] = {{"zadd",zaddCommand,-4,"write use-memory fast @sortedset",0,NULL,1,1,1,0,0,0},
}

redis/src/t_zset.c 的zaddGenericCommand方法会调用一个关键的方法,就是zsetAdd,这个方法会判断元素存储数据结构是ziplist还是skiplist。如果zset集合的元素个数大于redis.config文件设置的zset_max_ziplist_entries大小或者zset集合元素的member的长度大于redis.config设置的zset_max_ziplist_value大小,就会使用skiplist数据结构存储。

void zaddCommand(client *c) {zaddGenericCommand(c,ZADD_NONE);
}int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {//......省略if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {//......省略} else if (!xx) {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);//......省略} else {//......省略}} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {//......省略} else {//......省略}//......省略
}

redis/conf文件可以设置这两个参数的大小。
为了节省空间,当zset集合的元素个数少于128个并且zadd的member的长度小于64字节,使用ziplist数据结构存储元素,否则使用skiplist数据结构存储元素

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

尝试修改zset-max-ziplist-value的值为3,修改完后重启redis-server。向命名为test的zset集合中依次添加元素A、BB、CCC、DDDD,使用OBJECT ENCODING test(集合名称)查看当前使用的数据结构,数据结构在插入第四个节点后修改为skiplist,因为字符串DDDD的长度大于3。

zskiplist

redis/src/server.c 文件中定义了zskiplistNode和zskiplist的结构。

(1)zskiplistNode包括字符串ele、浮点类型的分值score、后退指针backward和层级数组lelel。每一个层级包括前进指针和跨度。在查找某个节点的过程中, 将沿途访问过的所有层的跨度累计起来, 得到的结果就是目标节点在跳跃表中的排位。

(2)zskiplist包括头结点、尾结点、包含结点个数和结点(非头结点)最大层级数。

typedef struct zskiplistNode {sds ele;double score;struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span;} level[];
} zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

借用《Redis设计与实现》描述跳跃表的一张图。
(1)头结点包含32个层级,但不存储实际元素。
(2)包含o1、o2、o3三个元素。
(3)查找o3可以直接通过第5级索引查找。

用Java实现跳跃表

自己尝试去实现了一个跳跃表,只实现了一个插入和查找的方法,没有考虑重复元素的情况。

import lombok.Builder;
import lombok.Data;import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;@Data
@Builder
class SkipListNode {private String ele;private double score;private SkipListNode backward;private SkipListLevel level[];
}@Data
@Builder
class SkipList {private SkipListNode header;private SkipListNode tail;private long length;private int level;
}@Data
@Builder
class SkipListLevel {private SkipListNode forward;private long span;
}@Data
@Builder
class ZSet {private Map<String, SkipList> data;//跳跃表头结点的最大层级是32.public static final int SIZE = 3;//添加元素,先判断name这个集合是否存在,如果不存在就初始化public SkipListNode add(String name, String ele, double score) {SkipList skipList = data.get(name);if(null == skipList) {//初始化skipList = initSkipList();data.put(name, skipList);}//随机生成一个层级Random random = new Random();int level = random.nextInt(SIZE);//从头结点的层级开始遍历SkipListNode skipListNode = SkipListNode.builder().ele(ele).score(score).level(initSkipListLevel(level + 1)).build();for (int i = level; i >= 0; i--) {//计算排位double rank = 0;SkipListLevel prevLevel = skipList.getHeader().getLevel()[i];if(null == prevLevel.getForward()) {prevLevel.setForward(skipListNode);prevLevel.setSpan((long) score);continue;}SkipListNode prevNode = prevLevel.getForward();while(null != prevLevel.getForward() && score >= (rank + prevLevel.getSpan())) {rank += prevLevel.getSpan();prevNode =  prevLevel.getForward();prevLevel = prevLevel.getForward().getLevel()[i];}//插入结点if(null != prevLevel.getForward()) {skipListNode.getLevel()[i].setForward(prevLevel.getForward());skipListNode.getLevel()[i].setSpan((long) (prevLevel.getForward().getScore() - skipListNode.getScore()));}prevNode.getLevel()[i].setForward(skipListNode);prevNode.getLevel()[i].setSpan((long) (skipListNode.getScore() - prevNode.getScore()));}return skipListNode;}public SkipListNode find(String name, double score) {SkipList skipList = data.get(name);if(null == skipList) {return null;}int fromIndex = SIZE - 1;SkipListNode prevNode = skipList.getHeader();while(null != prevNode) {SkipListLevel skipListLevel = prevNode.getLevel()[fromIndex];SkipListNode forwardNode = skipListLevel.getForward();if(null == forwardNode || forwardNode.getScore() > score) {fromIndex--;} else if(forwardNode.getScore() == score) {System.out.println("在第" + (fromIndex + 1) + "级索引找到:(" + forwardNode.getEle() + "," + forwardNode.getScore() + ")==>");return forwardNode;} else {System.out.println("在第" + (fromIndex + 1) + "级索引找到:(" + forwardNode.getEle() + "," + forwardNode.getScore() + ")==>");prevNode  = forwardNode;}}return null;}private SkipList initSkipList() {//设置头结点SkipListNode header = SkipListNode.builder().level(initSkipListLevel(SIZE)).build();SkipList skipList =  SkipList.builder().header(header).build();return skipList;}private SkipListLevel[] initSkipListLevel(int level) {SkipListLevel levels[] = new SkipListLevel[level];for (int i = 0; i < level; i++) {levels[i] = SkipListLevel.builder().build();}return levels;}}public class SkipListTest {public static void main(String[] args) {ZSet zSet = ZSet.builder().data(new ConcurrentHashMap<>()).build();zSet.add("test", "A", 10);zSet.add("test", "B", 20);zSet.add("test", "C", 30);zSet.add("test", "D", 40);zSet.add("test", "E", 50);SkipList skipList = zSet.getData().get("test");SkipListNode header = skipList.getHeader();SkipListLevel[] levels = header.getLevel();for (int i = levels.length - 1; i >= 0 ; i--) {SkipListLevel level = levels[i];SkipListNode node = level.getForward();while(null != node) {System.out.print("(" + node.getEle() + "," + node.getScore() + ") ");node = node.getLevel()[i].getForward();}System.out.println();}zSet.find("test", 50);}
}

运行结果:

为了避免生成太多层级索引,我将生成的最大层级限制改为了3。运行一次,用随机数生成结点的最大层级索引。这里结点A有三个层级,结点B有3级索引,结点C有1级索引,结点D有1级索引,结点E有2级索引。查找结点E,会经过3级索引(A,10)、(B,20)、2级索引(E,50)。比只有1级索引需要经过(A,10)、(B,20)、(C,30)、(D,40)、(E,50)快。

参考

Redis 跳跃表的实现
后台开发第七十八讲|redis,有序集合(orderedset),跳表,面试,源码学习一节课搞定1. 跳表的演变 2. 跳表的实现 3. redis中zset实现

Redis中的zset原理以及用Java实现跳跃表相关推荐

  1. Redis中对ZSet类型的操作命令

    写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- ------------ ...

  2. 使用redis中的zset进行金牌、银牌、铜牌的排序操作

    通过使用redis中的zset进行金牌.银牌.铜牌的排序操作 简介: 1. Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员. 2.不同的是每个元素都会关联一个doub ...

  3. zset获取指定score_7、Redis中对ZSet类型的操作命令

    写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- ------------ ...

  4. 用Redis中的zset实现一个限流器

    你被限流过吗 我还记得14年抢红米的时候,下面这个图是我最烦的一个图 抢了两个星期,才终于买到了我的第一台小米手机:红米1s.小米商城加入了一个排队的机制,于是我们可以感知到自己被限流了,但大部分服务 ...

  5. Redis中的zset 存储结构(实现)原理

    同时满足以下条件时使用ziplist 编码: 元素数量小于128 个 所有member 的长度都小于64 字节 在ziplist 的内部,按照score 排序递增来存储.插入的时候要移动之后的数据. ...

  6. Redis 中 bitmap 的原理和使用

    原理 先声明一下:Redis 有5种数据类型,而 BitMap 在 Redis 中并不是一个新的数据类型,其底层是 Redis 实现. 通常情况下,我们在 redis 中存储一个字符串,如:" ...

  7. Redis中的主从复制原理

    连接阶段 1.slave node 启动时(执行slaveof 命令),会在自己本地保存master node 的信息,包括master node 的host 和ip. 2.slave node 内部 ...

  8. Redis中的zset 有序集合

    存储类型 sorted set,有序的set,每个元素有个score. score 相同时,按照key 的ASCII 码排序. 数据结构对比: 数据结构 是否允许重复元素 是否有序 有序实现方式 列表 ...

  9. redis中的zset

    1.组成结构 跳跃表结构 2.接口API zslCreate:初始化zskipList的层数为1,长度为0,尾指针为NULL,创建zskiplistNode哨兵结点,结点层数为32,每层的前向指针为N ...

最新文章

  1. Paint.Net学习笔记——二、窗体(上)
  2. java运算符使用总结_Java运算符知识点总结
  3. vue 属性 computed
  4. Android中Dialog对话框
  5. 一种一致性HASH算法的实现方法,附核心代码
  6. 计算机科学与技术学科带头人,计算机专业学科带头人—陆玉昌教授
  7. 微信公众号发送红包(源码)
  8. 基于PHP和MySQL的奶茶网站,基于PHP和MySQL的网站设计与实现
  9. 流利阅读 2019.3.18 Can baijiu, China’s sorghum firewater, go global?
  10. python3GUI--打造一款音乐播放器By:PyQt5(附下载地址)
  11. 自学unity,该不该阻止?
  12. JAVA商城项目(微服务框架)——第11天 elasticsearch搜索
  13. 【苦练基本功】代码整洁之道 pt2(第4章-第6章)
  14. 【保姆级·创建对象】如何通过Supplier创建对象
  15. ip route常用语法
  16. 添加底图&切换底图——参考ArcGIS API
  17. Type safety: The expression of type List needs unchecked conversion to conform to List<TagFormal>
  18. 数据结构 c语言 严蔚敏 第十章 答案 10.1,严蔚敏版数据结构(C语言版)参考答案第十章..doc...
  19. 【UDK官方教程】知识点学习第一章
  20. 疯狂的极客--初识BadUSB

热门文章

  1. python实现caj转pdf代码
  2. 基于javaweb+jsp的蛋糕商城系统(java+JDBC+Servlet+HTML+Ajax+mysql+Fileupload)
  3. check_hostname requires server_hostname
  4. ECharts实现动态切换主题样式
  5. “机海战术”下智能手机成爆款越来越难,荣耀9还有戏吗?
  6. 队列的基本操作(全)
  7. 解析掌握现代化少儿编程实操能力
  8. 12小时超级马拉松赛记
  9. 你见过吗?9款超炫的复选框(Checkbox)效果
  10. Java把文件压缩然后下载