前言

参考资料:《Redis设计与实现 第二版》;

本篇笔记按照书里的脉络,将知识点分为四个部分。其中第一部分数据结构与对象分为上中下篇,上篇包括:SDS链表字典;中篇包括跳跃表整数集合压缩列表;下篇为对象

上篇的链接:https://www.cnblogs.com/dlhjw/p/15569578.html

下篇的链接:https://www.cnblogs.com/dlhjw/p/15594048.html

Redis常用命令及示例总结:https://blog.csdn.net/dlhjw1412/article/details/119713214


1. 跳跃表

  • 跳跃表支持平均 O(logN)、最坏 O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点;
  • 跳跃表的效率可以媲美平衡树,实现比平衡树简单;
  • 跳跃表在Redis里只有两个应用:有序集合键的底层实现、集群节点中用作内部数据结构;

1.1 跳跃表与其节点的定义

  • 跳跃表的定义,在redis.h/zskiplist结构里:

    typedef struct zskiplist {
    //表头节点和表尾节点
    structz skiplistNode *header, *tail;
    //表中节点的数量(不包括表头指针)
    unsigned long length;
    //表中层数最大的节点的层数(不包括表头指针)
    int level;
    } zskiplist;
  • 跳跃表节点的定义,在redis.h/zskiplistNode结构里:

    typedef struct zskiplistNode{
    //后退指针
    struct zskiplistNode *backwars;
    //分值
    double score;
    //成员对象
    robj *obj;
    //层
    struct zskiplistLevel{
    //前进指针
    struct zskiplistNode *forward;
    //跨度
    unsigned int apan;
    } level[];
    } askiplistNode;
    • 节点中使用L1L2L3等来标记节点的各个,每个层有前进指针和跨度;

      • 带数字的箭头为前进指针,数字为跨度
      • 一般来说,层数越多访问其他节点速度越快;
      • 创建新跳跃表节点时,随机生成介于1和32之间的数作为level数组的大小;
      • 跨度与遍历无关,与排位rank有关。查找某个节点时,将沿途层相加,得到排位;
    • BW字样的为后退指针
    • 1.02.03.0分值,分值从小到大排列;
      • 当分值相同时,成员对象在字典中排序小的靠近表头节点;
    • o1o2o3等是成员对象,成员对象必须唯一;
    • 表头节点也有后退指针、分值和成员对象,不会用到所以图中没有显示;
    • 下图中level为5是因为o3对象有5层,为该跳跃表中最大层;

1.2 跳跃表的API

函数 作用 时间复杂度
zslCreate 创建一个新的跳跃表 O(1)
zslFree 释放给定跳跃表,以及表中包含的所有节点 O(N),N为跳跃表的长度
zslInsert 将包含给定成员和分值的新节点添加到跳跃表中 平均O(logN),最坏O(N),N为跳跃表长度
zslDelete 删除跳跃表中包含给定成员和分值的节点 平均O(logN),最坏O(N),N为跳跃表长度
zslGetRank 返回包含给定成员和分值的节点在跳跃表中的排位 平均O(logN),最坏O(N),N为跳跃表长度
zslGetElementByRank 返回包含给定成员和分值的节点在跳跃表中的排位 平均O(logN) ,最坏O(N),N为跳跃表长度
zslIsInRange 给定一个分值范围(range),比如0到15,20到28,诸如此类,如果给定的分值范围包含在跳跃表的分值范围内,返回1,否则返回0 O(1),基于通过跳跃表的表头节点和表尾节点的分值得到范围
zslFirstInRange 给定一个分值范围,返回跳跃表中第一个符合这个范围的节点 平均O(logN),最坏O(N),N为跳跃表长度
zslLastInRange 给定一个分值范围,返回跳跃表中最后一个符合这个范围的节点 平均O(logN),最坏O(N),N为跳跃表长度
zslDeleteRangeByScore 给定一个分值范围,删除跳跃表中所有在这个范围之内的节点 O(N),N为被删除节点数量
zslDeleteRangeByRank 给定一个排位范围,删除跳跃表中所有在这个范围之内的节点 O(N),N为被删除节点数量

2. 整数集合

  • 整数集合 intset,其特点是从小到大保存整数且不会重复;
  • 整数集合在Redis里的应用:集合键的底层实现;

2.1 整数集合的实现

  • 整数集合的定义,在intset.h/intset结构中:

    typedef struct intset{
    //编码方式
    uint32_t encoding;
    //集合包含的元素数量
    uint32_t length;
    //保存元素的数组
    int8_t contents[];
    } intset;
    • contents声明为 int8_t 类型的数组,但数组的真正类型取决于encoding属性的值;
    encoding值 contents值 范围
    INTSET_ENC_INT16 int16_t -32768~32768
    INTSET_ENC_INT32 int32_t -2147483648~2147483647
    INTSET_ENC_INT64 int64_t -9223372036854775808~9223372036854775807

2.2 整数集合的类型升级

  • 当新增的元素类型比整数集合现有元素的类型长时,需要升级;
  • 步骤:
    • 根据新元素类型,扩展整数集合底层数组空间大小,并为新元素分配空间;
    • 将底层数组现有元素转换成新元素相同的类型,在维持集合有序性质不变情况下将转换后的元素放置到正确位置上;
    • 将新元素添加到底层数组里;
  • 因为添加新元素可能会引起升级,每次升级需要对所有元素进行类型转换,因此时间复杂度为O(N);
  • 因为引起升级操作的新元素比现有元素长,所以新元素要么添加到数组开头,要么数组末尾;
  • 好处:
    • 灵活性:C语言通常不会将不同类型值放在同一个数据结构里,Redis的升级使其可以;
    • 节约内存
  • 整数集合不允许降级操作;

2.3 整数集合的API

函数 作用 时间复杂度
intsetNew 创建一个新的整数集合 O(1)
intsetAdd 将给定元素添加到整数集合里面 O(N)
intsetRemove 从整数集合中移除给定元素 O(N)
intsetFind 检查给定值是否存在于集合 O(logN),整数集合有序排列,可以用二分查找法
intsetRandom 从整数集合中随机返回一个元素 O(1)
intsetGet 取出底层数组在给定索引上的元素 O(1)
intsetLen 返回整数集合包含的元素个数 O(1)
intsetBlobLen 返回整数集合咱用的内存字节数 O(1)

3. 压缩列表

  • 压缩列表 ziplist,其特点是管理小整数值和短字符串;
  • 压缩列表在Redis里的应用:列表键与哈希键的底层实现之一;
  • 压缩列表的Redis为节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构;

3.1 压缩列表的结构

  • 压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构;



3.2 压缩列表节点的定义

  • 节点的定义在ziplist.c/zlentry结构里:

    typedef struct zlentry {
    // prevrawlen :前置节点的长度
    // prevrawlensize :编码 prevrawlen 所需的字节大小
    unsigned int prevrawlensize, prevrawlen;
    // len :当前节点值的长度
    // lensize :编码 len 所需的字节大小
    unsigned int lensize, len;
    // 当前节点 header 的大小
    // 等于 prevrawlensize + lensize
    unsigned int headersize;
    // 当前节点值所使用的编码类型
    unsigned char encoding;
    // 指向当前节点的指针
    unsigned char *p;
    } zlentry;
    • 可以用当前节点地址减去prevrawlen的值获得前置节点的首地址,可以由此实现从尾到头的遍历;
    • *p指向一个content,保存节点的值,值的类型和长度由encoding决定;
    • encoding的属性(下划线表示留空,abcdx代表实际二进制数据):

3.3 连锁更新

  • 首先,压缩列表节点有个prevrawlen属性,用于记录前一个节点的长度,前一个节点的长度变化会影响prevrawlen属性的长度取值(使用1个字节存储前一个节点的长度还是5个);
  • 假设所有结点(e1, e2......eN)长度介于250~253字节之间,在表头新增长度大于等于254字节的new节点,因为e1的prevrawlen属性仅1字节,无法保存大于254的数字(new的长度),因此需要扩展为5字节长,此时e1的长度介于254~257字节之间。这样,new引发e1的扩展,e1引发e2的扩展,形成连锁更新;
  • 删除节点也可能引发连锁更新;
  • 连锁更新的最坏时间复杂度为 O(N2);
  • 在实际中,连锁更新造成的性能问题几率很低;

3.4 压缩列表的API

函数 作用 时间复杂度
ziplistNew 创建一个新的压缩列表 O(1)
ziplistPush 创建一个包含给定值的新节点,并将这个新节点添加到压缩列表的表头或表尾 平均O(N),最坏O(N2)
ziplistInsert 将包含给定值的新节点插入到给定节点之后 平均O(N),最坏O(N2)
ziplistIndex 返回压缩列表给定索引上的节点 O(N)
ziplistFind 在压缩列表中查找并返回包含了给定值的节点 当保存的是字节数字时为O(N2),整数时为O(N)
ziplistNext 返回给定节点的下一个节点 O(1)
ziplistPrev 返回给定节点的前一个节点 O(1)
ziplistGet 获取给顶节点说保存的值 O(1)
ziplistDelete 从压缩列表中删除给定的节点 平均O(N),最坏O(N2)
ziplistDeleteRange 删除压缩列表在给定索引上的连续多个节点 平均O(N),最坏O(N2)
ziplistBlobLen 返回压缩列表目前占用的内存字节数 O(1)
ziplistLen 返回压缩列表目前包含的节点数量 节点数量小于65535时为O(1),大于65535时为O(N)
  • 最坏时间复杂度为O(N2)是因为可能引发连锁更新;

最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

Redis | 第一部分:数据结构与对象 中篇《Redis设计与实现》的更多相关文章

  1. [redis读书笔记] 第一部分 数据结构与对象 对象类型

    - 从前面redis的基本数据结构来看,可以看出,redis都是在基本结构(string)的基础上,封装了一层统计的结构(SDS),这样让对基本结构的访问能够更快更准确,提高可控制度. - redis ...

  2. Redis笔记(1)数据结构与对象

    1.前言 此系列博客记录redis设计与实现一书的笔记,提取书本中的知识点,省略相关说明,方便查阅. 2.基本数据结构 2.1 简单动态字符串SDS(simple dynamic string) 结构 ...

  3. Redis 的底层数据结构(对象)

    目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层.最常用的数据结构,相信你也掌握的不错. 但 redis 实际存储键值对的时候,是基于对 ...

  4. 左手Mongodb右手Redis 第一章,进入Mongodb和Redis的世界

    ---恢复内容开始--- 1,为什么要使用非关系型数据库,关系型数据库咋滴,不能用嘛? 存在即合理,非关系型数据库的出现,那说明关系型数据库不适用了. 非关系型数据库(NOSQL)-->Not ...

  5. .net core工具组件系列之Redis—— 第一篇:Windows环境配置Redis(5.x以上版本)以及部署为Windows服务

    Cygwin工具编译Redis Redis6.x版本是未编译版本(官方很调皮,所以没办法,咱只好帮他们编译一下了),所以咱们先下载一个Cygwin,用它来对Redis进行编译. Cygwin下载地址: ...

  6. [REDIS 读书笔记]第一部分 数据结构与对象 跳跃表

    下面是跳跃表的基本原理,REDIS的实现大致相同 跳跃表的一个特点是,插入NODE是通过随机的方式来决定level的,比较奇特 下面是skipList的一个介绍,转载来的,源地址:http://ken ...

  7. [redis读书笔记] 第一部分 数据结构与对象 对象特性

    一 类型检查和多态    类型检查,即有的命令是只针对特定类型的,如果类型不对,就会报错,此处的类型,是指的键类型,即robj.type.下面为有类型检查的命令: 对于某一种类型,redis下底层的实 ...

  8. [redis读书笔记] 第一部分 数据结构与对象 字典

    三 字典 字典是Hash对象的底层实现,比如用HSET创建一个HASH的对象,底层可能就是用一个字典实现的键值对. 字典的实现主要设计下面三个结构: /* * 哈希表节点 */ typedef str ...

  9. [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串

    本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...

随机推荐

  1. 数据库DDL与DML对应含义

    DDL:指的是操作数据库.表.字段的相关语句,例如:create.alter.drop DML:指的是对表中的数据进行增删改的操作,例如:insert.update.delete 查询语句书写顺序:s ...

  2. PAT (Basic Level) Practice (中文)1022 D进制的A+B (20分)

    1022 D进制的A+B (20分) 输入两个非负 10 进制整数 A 和 B ( ≤ 2​30​​ −1),输出 A+B 的 D (1<D≤10)进制数. 输入格式: 输入在一行中依次给出 3 ...

  3. 高效动画实现原理-Jetpack Compose 初探索

    一.简介 Jetpack Compose是Google推出的用于构建原生界面的新Android 工具包,它可简化并加快 Android上的界面开发.Jetpack Compose是一个声明式的UI框架 ...

  4. Java(8)详解Random使用

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201556.html 博客主页:https://www.cnblogs.com/testero ...

  5. 将DataFrame赋值为可变变量在spark中多次赋值后运行速度减慢的问题

    该问题先标记上,之后有空了研究原因. 在var dataframe后将dataframe作为参数输入某方法,将结果重新赋予该dataframe,会导致spark运行显著减慢速度.暂时不知道原因,之后研 ...

  6. DevOps 时代的高效测试之路

    10 月 22 日,2021 届 DevOps 国际峰会在北京顺利召开,来自国内外的顶级技术专家共同畅谈 DevOps 体系与方法.过程与实践.工具与技术.CODING 测试及研发流程管理产品总监程胜 ...

  7. Java:String对象小记

    Java:String对象小记 对 Java 中的 String 对象,做一个微不足道的小小小小记 字节和字符的区别 字节 byte: 一个字节包含8个位(bit),因此byte的取值范围为-128~ ...

  8. mongodb的简单查询

    此篇文章简单的记录一下mongodb 的简单查询操作. 一.数据准备: db.persons.insertMany([ {'userId':1,name:'张三','age':20,'scores': ...

  9. CSS 奇技淫巧 | 巧妙实现文字二次加粗再加边框

    本文将通过一个实际的业务需求,讲解如何实现 极端场景下文字加粗加边框效果 文字多重边框的效果 需求背景 - 文字的二次加粗 今天遇到这样一个有意思的问题: 在文字展示的时候,利用了 font-weig ...

  10. Python课程笔记(六)

    今天上课补上了上次未学完比较重点的鼠标和键盘事件,同时开始学习运用turtle进行绘图. 本次课程的代码: https://gitee.com/wang_ming_er/python_course_l ...