redis源码剖析(四)跳表

文章目录

    • 整数集合
    • 跳跃表
    • 压缩列表
    • 总结

整数集合

当一个集合只包含整数,且这个集合的元素不多的时候,Redis 就会使用整数集合 intset 。首先看 intset 的数据结构:

typedef struct intset {// 编码方式uint32_t encoding;// 集合包含的元素数量uint32_t length;// 保存元素的数组int8_t contents[];
} intset;

其实 intset 的数据结构比较好理解。一个数据保存元素,length 保存元素的数量,也就是contents的大小,encoding 用于保存数据的编码方式。

通过代码我们可以知道,encoding 的编码类型包括了:

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

实际上我们可以看出来。 Redis encoding的类型,就是指数据的大小。作为一个内存数据库,采用这种设计就是为了节约内存。

既然有从小到大的三个数据结构,在插入数据的时候尽可能使用小的数据结构来节约内存,如果插入的数据大于原有的数据结构,就会触发扩容。

扩容有三个步骤:

  1. 根据新元素的类型,修改整个数组的数据类型,并重新分配空间
  2. 将原有的的数据,装换为新的数据类型,重新放到应该在的位置上,且保存顺序性
  3. 再插入新元素

整数集合不支持降级操作,一旦升级就不能降级了。

跳跃表

跳跃表是链表的一种,是一种利用空间换时间的数据结构。跳表平均支持 O(logN),最坏O(N)复杂度的查找。

跳表是由一个zskiplist 和 多个 zskiplistNode 组成。我们先看看他们的结构:

/* ZSETs use a specialized version of Skiplists */
/** 跳跃表节点*/
typedef struct zskiplistNode {// 成员对象robj *obj;// 分值double score;// 后退指针struct zskiplistNode *backward;// 层struct zskiplistLevel {// 前进指针struct zskiplistNode *forward;// 跨度unsigned int span;} level[];} zskiplistNode;/** 跳跃表*/
typedef struct zskiplist {// 表头节点和表尾节点struct zskiplistNode *header, *tail;// 表中节点的数量unsigned long length;// 表中层数最大的节点的层数int level;} zskiplist;

所以根据这个代码我们可以画出如下的结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8veqZuZy-1573628505436)(media/15663755251342/15663757297856.jpg)]

其实跳表就是一个利用空间换时间的数据结构,利用 level 作为链表的索引。

之前有人问过 Redis 的作者 为什么使用跳跃表,而不是 tree 来构建索引?作者的回答是:

  1. 省内存。
  2. 服务于 ZRANGE 或者 ZREVRANGE 是一个典型的链表场景。时间复杂度的表现和平衡树差不多。
  3. 最重要的一点是跳跃表的实现很简单就能达到 O(logN)的级别。

压缩列表

压缩链表 Redis 作者的介绍是,为了尽可能节约内存设计出来的双向链表。
对于一个压缩列表代码里注释给出的数据结构如下:
在这里插入图片描述

zlbytes 表示的是整个压缩列表使用的内存字节数zltail 指定了压缩列表的尾节点的偏移量zllen 是压缩列表 entry 的数量entry 就是 ziplist 的节点zlend 标记压缩列表的末端

这个列表中还有单个指针:

ZIPLIST_ENTRY_HEAD 列表开始节点的头偏移量ZIPLIST_ENTRY_TAIL 列表结束节点的头偏移量ZIPLIST_ENTRY_END 列表的尾节点结束的偏移量

再看看一个 entry 的结构:

/** 保存 ziplist 节点信息的结构*/
typedef struct zlentry {// prevrawlen :前置节点的长度// prevrawlensize :编码 prevrawlen 所需的字节大小unsigned int prevrawlensize, prevrawlen;// len :当前节点值的长度// lensize :编码 len 所需的字节大小unsigned int lensize, len;// 当前节点 header 的大小// 等于 prevrawlensize + lensizeunsigned int headersize;// 当前节点值所使用的编码类型unsigned char encoding;// 指向当前节点的指针unsigned char *p;} zlentry;

依次解释一下这几个参数。

prevrawlen 前置节点的长度,这里多了一个 size,其实是记录了 prevrawlen 的尺寸。Redis 为了节约内存并不是直接使用默认的 int 的长度,而是逐渐升级的。
同理 len 记录的是当前节点的长度,lensize 记录的是 len 的长度。
headersize 就是前文提到的两个 size 之和。
encoding 就是这个节点的数据类型。这里注意一下 encoding 的类型只包括整数和字符串。
p 节点的指针,不用过多的解释。

需要注意一点,因为每个节点都保存了前一个节点的长度,如果发生了更新或者删除节点,则这个节点之后的数据也需要修改,有一种最坏的情况就是如果每个节点都处于需要扩容的零界点,就会造成这个节点之后的节点都要修改 size 这个参数,引发连锁反应。这个时候就是 压缩链表最坏的时间复杂度 O(n^2)。 不过所有节点都处于临界值,这样的概率可以说比较小。

总结

至此Redis的基本数据结构就介绍完了。我们可以看到 Redis 对内存的使用真是“斤斤计较”,对于内存是使用特别节约。同时 Redis 作为一个单线程应用,不用考虑并发的问题,将很多类似 size 或者 length 的参数暴露出来,将很多 O(n) 的操作降低为 O(1)。

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

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

相关文章

vivo C/C++工程师 HR视频面试问题总结20180807

一开始没想到这次视频面是HR面试,还以为是技术面试,毕竟上次面试的时候技术问题问的相对比较少,所以面试准备方向有点儿错了,不过还是总结一下具体问题。 1)自我介绍:吸取了上次自我介绍的经验,…

在Redis客户端设置连接密码 并演示密码登录

我们先连接到Redis服务 然后 我们要输入 CONFIG SET requirepass “新密码” 例如 CONFIG SET requirepass "A15167"这样 密码就被设置成立 A15167 我们 输入 AUTH 密码 例如 AUTH A15167这里 返回OK说明成功了 然后 我们退出在登录就真的需要 redis-cli -h IP地…

redis源码剖析(五)—— 字符串,列表,哈希,集合,有序集合

文章目录对象REDIS_STRING (字符串)REDIS_LIST 列表REDIS_SET (集合)REDIS_ZSET (有序集合)REDIS_HASH (hash表)int refcount(引用计数器)unsigned lru:REDIS_LRU_BITS对象 对于 Re…

函数sscanf小结

1.sscanf用于处理固定格式的字符串&#xff0c;包含在头文件<cstdio>中&#xff0c;函数原型为&#xff1a; int sscanf(const char *buffer,const char*format,[]argument ]...); 其中:buffer代表着要存储的数据&#xff0c;format 代表格式控制字符串&#xff0c;arg…

redis源码剖析(六)—— Redis 数据库、键过期的实现

文章目录数据库的实现数据库读写操作键的过期实现数据库的实现 我们先看代码 server.h/redisServer struct redisServer{...//保存 db 的数组redisDb *db;//db 的数量int dbnum;... }再看redisDb的代码&#xff1a; typedef struct redisDb {dict *dict; /*…

多益网络 视频面试面试总结20180816

1.首先是自我介绍&#xff1a;因为等了半个小时&#xff0c;所以有点儿紧张&#xff0c;只说了一下自己的学校&#xff0c;爱好和兴趣&#xff1b; 2.介绍了一个自己的最成功的项目&#xff1a;我介绍了一个关于GPS导航的项目&#xff0c;介绍了项目的内容和项目的一些工作&am…

redis源码剖析(七)—— Redis 数据结构dict.c

文章目录dict.hdict.cdict.h //定义错误相关的码 #define DICT_OK 0 #define DICT_ERR 1//实际存放数据的地方 typedef struct dictEntry {void *key;void *val;struct dictEntry *next; } dictEntry;//哈希表的定义 typedef struct dict {//指向实际的哈希表记录(用数组开链的…

简述linux中动态库和静态库的制作调用流程

假设现在有这些文件&#xff1a;sub.c add.c div.c mul.c mainc head.h&#xff08;前4个.C文件的头文件&#xff09; 1.静态库制作流程 gcc -c sub.c add.c div.c mul.c -->生成 .o目标文件文件 ar rcs libmycal.a *.o …

redis源码剖析(八)—— 当你启动Redis的时候,Redis做了什么

文章目录启动过程初始化server结构体main函数会调用initServer函数初始化服务器状态载入持久化文件&#xff0c;还原数据库开始监听事件流程图启动过程 初始化server结构体从配置文件夹在加载参数初始化服务器载入持久化文件开始监听事件 初始化server结构体 服务器的运行ID…

linux中错误总结归纳

1.使用gcc编译C文件&#xff0c;C文件在for循环语句中出现变量定义 编译器提示错误&#xff1a;“for”loop initial declarations are only allowed in C99 mode. note:use option -stdc99or-stdgnu99 to compile; 原因&#xff1a;gcc的标准是基于c89的&#xff0c;c89不能在…

redis源码剖析(十一)—— Redis字符串相关函数实现

文章目录初始化字符串字符串基本操作字符串拼接操作other获取指定范围里的字符串将字符串中的所有字符均转为小写的形式将字符串中所有字符均转为大写的形式字符串比较other#define SDS_ABORT_ON_OOM#include "sds.h" #include <stdio.h> #include <stdlib.…

makefile内容小结

makefile中每个功能主要分为三部分&#xff1a;目标&#xff0c;依赖条件和命令语句 1.支持对比更新的Makefile写法&#xff08;只会编译文件时.o文件和.c文件时间不一致的文件&#xff09; 2.使用makefile自动变量和自定义变量的makefile写法 其中&#xff1a;这三个符号为ma…

事务隔离级别动图演示

事务的基本要素&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09; 事务开始后所有操作&#xff0c;要么全部做完&#xff0c;要么全部不做&#xff0c;不可能停滞在中间环节。事务执行过程中出错&#xff0c;会回滚到事务开始前的状态&#xff0c;所有的…

C/C++的优点和缺点

1.C/C语言的优点 C语言是面向过程的语言&#xff0c;常用来编写操作系统。C语言是从C语言发展过来的&#xff0c;是一门面向对象的语言&#xff0c;它继承了C语言的优势&#xff0c;同时也添加了三个主要的内容&#xff1a;Oriented-Object class,Template,STL. 1)C/C可以潜入…

C/C++命令行参数那点事

int main(int argc, char *argv[ ]); 1.命令行参数&#xff1a;在命令行中给定的参数&#xff1b; 2.命令行参数在对函数main的调用时&#xff0c;主要有两个参数送到main,一个是argc(argument count),命令行参数的个数&#xff0c;另外一个是argv,命令行参数的数组,命令行参…

mysql row_id为什么是6字节?为什么是8字节

mysql row_id是几个字节&#xff1f; row_id InnoDB表中在没有默认主键的情况下会生成一个6字节空间的自动增长主键 row_id是整型还是字符型&#xff1f; 源代码中 row_id 是 ib_uint64_t 这是 8字节 uint64_t 是整形 为什么是6个字节&#xff1f; P.S. Base64编码说明 B…

linux中的man文档结构

使用命令 man chapter章节号查找的内容

伪随机数和真随机数

伪随机数小项目 猜数字游戏 //C语言 猜数字游戏 https://blog.csdn.net/csdn_kou/article/details/79785709 C语言之随机数生成超详解 https://blog.csdn.net/csdn_kou/article/details/79788815 在上面的文章中&#xff0c;使用固定函数就一直是生成固定的随机结果&#…

linux中的IO函数

1)open函数&#xff1a;以特定的方式打开一个文件&#xff1b; 头文件&#xff1a;sys/type.h sys/stat.h fcntl.h 返回值&#xff1a;错误则返回-1&#xff0c;正确则返回文件描述符&#xff08;int类型&#xff0c;范围为3~1023,文件的标号&#xff09; 函数原型&#xff…

ps -ef和ps aux

ps -ef和ps aux ps -ef unix风格 -e 列出所有进程 -f 完整格式 UID PID PPID C STIME TTY TIME CMD root 1 0 0 8月27 ? 00:25:08 /usr/lib/systemd/systemd --switched-root --system --deserialize 22 root 2 0 0 8月…