当前位置: 首页 > news >正文

Redis:数据结构

简单动态字符串SDS

Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构

建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符

串表示。

SDS 的实现

struct sdshdr{// 记录buf中已使用字节数量等于sds保存字符串的长度int len;// 记录buf数组中未使用字节的数量int free;// 字节数组,保存字符串char buf[];
}

SDS 与 C字符串的区别

  1. 常数复杂度获取字符串的长度。sds中记录了字符串长度可以直接获取
  2. 杜绝缓存区溢出。sds的空间分配完全杜绝了发生缓冲区溢出的可能性,当sds API需要对SDS进行修改的时候,API会先检查SDS的空间是否满足修改的需要,如果不满足,会自动将SDS的空间扩展至执行修改所需的大小。
  3. 减少修改字符串时带来的内存冲分配次数。
    1. 空间预分配。优化SDS字符串增长操作,当SDS需要进行空间扩展的时候,程序不仅会为SDS分配修改锁必须要的空间,还会为SDS分配额外的未使用空间。分配公式
      1. 如果对SDS进行修改之后,SDS的长度小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS的len属性的值和free相同
      2. 如果对SDS进行修改之后,SDS的长度大于1MB,那么程序会分配1MB的未使用空间。
    2. 惰性空间的释放。用于优化SDS的字符串缩短操作,当SDS缩短的时候,程序不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来。等待将来使用。SDS API提供的有真正释放空间的方法。
  4. 二进制安全。C字符串必须符合某种编码,并且除字符串末尾外不能包含空字符,只能保存文本数据。SDS通过len判断字符串末尾。SDS不是保存字符而是保存二进制,所以SDS不仅可以保存文本数据还可以保存二进制。

链表

链表的实现

链表节点的结构:

typedef struct listNode{struct listNode *prev;struct listNode *next;void *value;
}listNode;

持有链表的结构:

typedef struct list{listNode *head;listNode *tail;unsigned long len;// 节点值复制函数void *(*dup)(void *ptr);// 节点值释放函数void (*free)(void *ptr);// 节点值对比函数int (*match)(void *ptr, void *key);
}list;

Redis 链表的特点

  • 双向:双向列表。
  • 无环:表头的prev指针和表尾的next指针都执行null。
  • 长度计数:获取节点数量的复杂度为O(1)
  • 多态:链表节点使用void*指针来保存节点值,可以通过list结构的dup,free,match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

字典(map)

字典的实现

Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

哈希表的结构定义:

typedef struct dictht{//哈希表数组dictEntry **table;// 哈希表的大小unsigned long size;// 哈希表大小掩码,用于计算索引,总是等于size- 1unsigned long sizemask;// 哈希表已有节点的数量unsigned long used;
}

table属性是一个数组,数组中的每一个元素都是一个指向dict.h/dictEntry结构,每个dictEntry结构保存一个键值对。

哈希表节点结构定义:

typedef struct dictEntry{void *key;union{void *val;unit64_tu64;int64_ts64;}v;// 指向下一个哈希表节点,形成链表 struct dictEntry *next;
}

字典结构的定义:

typedef struct dict{// 类型特定函数dictType *type;// 私有数据void *privadata;// 哈希表dictht ht[2];// rehash 索引 当rehash不再进行时,值为-1int trehashidx;
}
  • type 是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
  • privdata保存了需要传给那些类型特定函数的可选参数。
  • ht[2] 保存了两个hash表,一般情况下只使用h[0], 在进行rehash的时候会使用到h[1]
  • trehashidx 记录了rehash的进度。

解决hash冲突

使用链地址法解决hash冲突。

rehash

哈希表保存的键值对数量太多或者太少的时候将要执行rehash(重新散列)

rehash的步骤:

  1. 为字典的ht[1] 哈希表分配空间,这个哈希表的大小决定于要执行的操作以及ht[0] 当前包含的键值对数量(即ht[0].used属性的值)、
    1. 如果执行的扩展操作,ht[1].size = 第一个大于等于ht[0].used * 2的2的n次幂
    2. 收缩操作,ht[1] = 第一个大于等于ht[0].used * 2的2的n次幂
  2. 保存在ht[0]中的键值对重新rehash到ht[1]上。
  3. 迁移完成之后,释放ht[0] 将 ht[1] 设置为ht[0],并在ht[1]新创建一个空白哈希表。

什么时候进行扩展与收缩呢?

程序自动对哈希表扩展的条件(满足一条即可):

  1. 服务器目前没有在执行BGSAVE或者BGREWRITEAOF命令,并且哈希表负载因子大于等于1
  2. 服务器在执行BGSAVE或者BGREWRITEAOF命令,并且哈希表负载因子大于等于5

负载因子(factor) = 哈希表以保存节点数量 / 哈希表大小。(ht[0].used / ht[0].size)

自动收缩的条件:负载因子小于0.1

渐进式rehash

在字典中维持一个索引计数器变量rehashidx,通过这个变量进行rehash。

首先将rehashidx设置为0,表示rehash开始。在rehash期间,每次对字典进行添加,删除,查找或者更新操作时,顺带进行rehash 即将ht[0]哈希表在rehashidx索引上的键值对rehash到ht[1]上 ,rehashidx逐渐增加,所有的rehash之后将rehashidx设置为-1表示结束。

这种方式避免了集中式的rehash带来的庞大工作量。在进行使用中逐渐的rehash完成。

在渐进式rehash过程中,字典会同时使用ht[0]和ht[1]两个哈希表。例如查找回去两个哈希表中进行。新添加的只保存到ht[1]中。最终ht[0]变为空表。

跳跃表

跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。在Redis中用于实现有序集合。

跳跃表的实现

跳跃表由zskiplistNode和zskiplist两个结构定义。

在这里插入图片描述

header指向的节点是表头节点,表头节点只有层,其它属性不会使用。

跳跃表节点的实现:

typedef struct zskiplistNode{// 层struct zskiplistLevel{//前进指针struct zskiplistNode *forward;//跨度unsigned int span;} level[];// 后退指针struct zskiplistNode *backward;//分值double score;//成员对象robj *obj;
}zskiplistNode;
  1. 层 level[]。通过层加快访问其它节点的速度。数组中的每个元素包含一个指向其它节点的指针和与指向节点的跨度。每次创建一个新跳跃表节点的时候,程序根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的高度。
    1. **前进指针。**每个层都有一个指向表尾方向的前进指针。
    2. **层的跨度:**用于记录两个节点之间的距离。
  2. **后退指针。**用于表尾向表头方向访问节点。每次只能回退至前一个节点。
  3. **分值和成员。**节点的分值是一个double类型的浮点数,跳跃表中的所有节点都按分值从小到大来排序。节点的成员对象是一个指针,它指向一个字符串对象保存着一个SDS值。成员对象必须是唯一的,分值是可以相同的。

跳跃表结构定义:

typedef struct zskiplist{//表头节点和表尾节点struct zskiplistNode *header, *tail;//表中节点的数量unsigned long length;//表中层数最大的节点层数(不包括表头节点)int level;
}zskiplist;

通过这个结构持有跳跃表节点。

整数集合

整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。

整数集合的实现

整数集合是Redis用于保存整数数值的集合抽象数据结构,它可以保存int16_t,int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。

intset结构表示一个整数集合:

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

contents 数组是整数集合的底层实现,整数集合的每个元素都是contents数组的一个数组项(item)各个项在数组中按值的大小从小到大有序地排列,并且数组中不会包括任何重复项。contents属性声明为int8_t类型的数组,但实际上contents数组不保存任何int8_t类型的数组,contents数组的真正类型取决于encoding属性的值。

升级

每当将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现在所有元素的类型都要长时,整数集合需要先进行升级(upgrade)然后才能将新元素添加到整数集合里面。

升级整数集合并添加新元素分为三步进行:

  1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
  2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确位置上。
  3. 将新元素加到底层数组里面。

升级的好处:(1)提升整数集合的灵活性(使用者不用考虑整数的类型,底层会自动升级类型,不用担心类型错误)。(2)尽可能的节约内存。

整数集合不支持降级,升级后,编码就会一直保持升级后的状态。

压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

压缩列表的实现

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。

在这里插入图片描述

压缩列表节点的构成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

节点的previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。

压缩列表从表尾向表头遍历操作就是通过这一原理实现的。

encoding属性记录了节点的content属性所保存数据的类型以及长度。

在这里插入图片描述

content复制保存节点的值,可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

对象

上面介绍了Redis用到的所有主要数据结构,Redis并没有字节使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串,列表,哈希,集合,有序集合这五种类型的对象,每一种对象都至少用到了一种前面介绍的数据结构

Redis对象系统还是先了**基于引用计数技术的内存回收机制。**对象的引用计数属性还带有对象共享的作用。

集合对象底层实现可以是intset(整数集合)或者hashtable

有序集合对象底层实现可以是ziplist或者skiplist。还包含一个用字典为有序集合创建了一个从成员到分值的映射。

哈希对象底层实现可以是ziplist或者hashtable

列表对象底层实现可以是ziplist或者linkedlist

对象的空转时长,对象结构包含一个lru属性记录对象最后一次被命令程序访问的时间。

服务器打开了maxmemory选项时,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被释放,从而回收内存。

参考《Redis设计与实现》

相关文章:

Redis:数据结构

简单动态字符串SDS Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构 建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符 串表示。 SDS 的实现…...

2.18 设置language和中文输入法

文章目录一:设置language二:设置中文输入法一:设置language nvidia的开发板上默认只有English,需要点击如下管理: 接着进入如下界面: 此时图中的“汉语(中国)”应该是没有的&…...

图解LeetCode——剑指 Offer 28. 对称的二叉树

一、题目 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 二、示例 2.1> 示例 1: 【输入】root [1,2,2,3,4,4,3] 【输出】true 2.2> 示例 2: 【输入】root [1,2,2,nul…...

Qt Desginer布局方法

首先将我们需要的控件拖拽到一个合适的位置,该例子中用到了两个label,两个lineEdit和两个pushButton。 然后我们需要利用弹簧来控制控件到控件之间的距离以及控件到窗体边界的距离,因为这里只有一组控件(两个label,两个…...

C/C++、Java、Python的比较及学习(3)

函数间的值传递与地址传递 值传递方式:指主调函数把实参的值赋给形参。 在这种传递方式下,主调函数中的实参地址与被调函数中的形参地址是相互独立的。 函数被调用时,系统为形参变量分配内存单元,并将实参的值存入到对应形参的内存…...

智慧校园建设方案

第一章、 智慧教学 6.1. 校本资源库 提供校本资源管理功能,实现学校内的教学资源的共建共享,促进教师之间的交流学习,提升学校的整体教学水平。在本系统中学校可以统一采购资源接入到校本资源库中供教师下载使用,教师也可以将…...

ARM uboot 源码分析5 -启动第二阶段

一、start_armboot 解析6 1、console_init_f (1) console_init_f 是 console(控制台)的第一阶段初始化。_f 表示是第一阶段初始化,_r 表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,…...

【ip neigh】管理IP邻居( 添加ARP\NDP静态记录、删除记录、查看记录)

一、邻居管理存在状态 1、NUD_NONE: 初始状态。当一个新的路由缓存条目被创建时,arp_bind_neighbour()函数被调用.如果找不到相匹配的ARP缓存条目, neigh_alloc()将创建一个新的ARP缓存条目并设置状态为NUD_NONE. 2、NUD_INCOMPLETE:未完成状…...

Java程序员线上排查问题神器-Arthas

文章目录前言一、Arthas是什么?二、快速入门1.下载2.如何运行三、常用命令1.dashboard2.trace总结前言 最近公司项目版本迭代升级,在开发新需求导致没什么时间写博客。 在开发需求的过程中,我写了一个接口,去批量调内部已经写好…...

上市公司企业持续创新能力、创新可持续性(原始数据+计算代码+计算结果)(2008-2021年)

数据来源:自主计算 时间跨度:2008-2021年 区域范围:沪深A股上市公司 指标说明: 参考何郁冰(2017)[1]的做法,将持续创新作为独立研究变量,同时采用创新投入指标(研发经费) 和创新…...

华为OD机试 - 考古学家(JS)

考古学家 题目 有一个考古学家发现一个石碑 但是很可惜 发现时其已经断成多段 原地发现N个断口整齐的石碑碎片 为了破解石碑内容 考古学家希望有程序能帮忙计算复原后的石碑文字组合数 你能帮忙吗 备注: 如果存在石碑碎片内容完全相同,则由于碎片间的顺序不影响复原后的碑…...

Leetcode.2100 适合打劫银行的日子

题目链接 Leetcode.2100 适合打劫银行的日子 Rating : 1702 题目描述 你和一群强盗准备打劫银行。给你一个下标从 0开始的整数数组 security,其中 security[i]是第 i天执勤警卫的数量。日子从 0开始编号。同时给你一个整数 time。 如果第 i天满足以下所…...

linux ubuntu查日志信息以及错误排查

目录 一、linux的日志文件 1、常用日志文件 2、其他日志文件 二、历史日志的查看 1、查看Logrotate的配置信息 2、查看日志配置 一、linux的日志文件 Linux系统中最有趣的(可能也是最重要的)目录之一是/var/log。根据文件系统层次结构标准,在系统中运行的大多数…...

DOS经典软件,落下帷幕,新型国产平台,蓬勃发展

提起DOS时代,总让人难以忘怀,陷入深深回忆中,风靡一时的许多软件,如今早已不在,这几款被称为DOS必装的软件,更是让人惋惜。 你还记得这图吗?堪称DOS系统最经典的软盘复制与映像生成软件&#xf…...

MongoDB数据存储格式

前言 之前分享了MongoDB的基本命名和视图等信息,本文分享一下MongoDB的数据存储类型,使用方式。基础的MongoDB信息就学习完啦,之后会继续分享MongoDB进阶的一些东西。 MongoDB数据存储格式前言1 文件结构1.2 字段名称2 点符号2.2 嵌入式文件…...

ARC126D Pure Straight

ARC126D Pure Straight 题目大意 给一个长度为nnn的整数序列A(a1,a2,…,an)A(a_1,a_2,\dots,a_n)A(a1​,a2​,…,an​),其中ai∈[1,k]a_i\in [1,k]ai​∈[1,k]。 你可以做如下操作任意次: 交换相邻两个元素 求最小的操作次数,使得序列AA…...

基于RK3588的嵌入式linux系统开发(四)——uboot镜像下载(基于RKDevTool工具)

官方提供的SDK中包含RKDevTool工具(RKDevTool_Release_v2.92)和相应的驱动(DriverAssitant_v5.1.1)。本节主要介绍在windows操作系统环境下利用RKDevTool下载以上生成的uboot镜像和bootloader镜像。注意:本节使用的板卡…...

设计模式之策略模式与责任链模式详解和应用

目录1.策略模式1.1 目标1.2.内容定位1.3.定义1.4.应用场景1.5.促销优惠业务场景1.6 用策略模式实现选择支付方式的业务场景1.7 策略模式在框架源码中的体现1.8 策略模式的优缺点2 责任链模式2.1 责任链楼式的应用场景2.2 利用责任链模式进行数据校验拦截2.3 责任链模式和建造者…...

广度优先搜索(BFS)-蓝桥杯

一、BFS搜索的原理BFS搜索的原理:“逐层扩散”,从起点出发,按层次从近到远,逐层先后搜索。编码:用队列实现。应用:BFS一般用于求最短路径问题,BFS的特点是逐层搜索,先搜到的层离起点…...

Java Type类

文章目录Type简介Type分类1. 原始类型(Class)2. 参数化类型(ParameterizedType)3. 类型变量(TypeVariable)4. 通配符类型(WildcardType)5. 泛型数组类型(GenericArrayType)Type简介 Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型…...

Springboot扩展点之CommandLineRunner和ApplicationRunner

Springboot扩展点系列:Springboot扩展点之ApplicationContextInitializerSpringboot扩展点之BeanFactoryPostProcessorSpringboot扩展点之BeanDefinitionRegistryPostProcessorSpringboot扩展点之BeanPostProcessorSpringboot扩展点之InstantiationAwareBeanPostPro…...

ngixn 常用配置之文件类型与自定义 log

大家好,我是 17 。 总结了一些 nginx 的常用配置。从入口文件开始,今天讲一下文件类型和自定义log 为了讲述方便,环境为 CentOS 7, nginx 版本 1.21。 配置文件入口 /etc/nginx/nginx.conf这是入口文件,这个文件里…...

【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出

Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…...

0.3调试opencv源码的两种方式

调试opencv源码的两种方式 上两篇我们分别讲了如何配置opencv环境,以及如何编译opencv源码方便我们阅读。但我们还是无法调试我们的代码,无法以我们的程序作为入口来一步一步单点调试看opencv是如何执行的。 【opencv源码解析0.1】VS如何优雅的配置ope…...

Redis的常见操作和Session的持久化

安装Redis使用yum命令,直接将redis安装到linux服务器:yum -y install redis启动redis使用以下命令,以后台运行方式启动redis:redis -server /etc/redis.conf &操作redis使用以下命令启动redis客户端:redis-cli设置…...

TypeScript笔记(二)

背景 上一篇文章我们介绍了TypeScript的一些特性,主要是其与JavaScript的比较,接下来我们将会开始学习Type的语法,这篇文章将会介绍TypeScript的数据类型。 原始数据类型 TypeScript是JavaScript的超集,TypeScript的数据类型就…...

【MyBatis】源码学习 03 - 类型处理器 TypeHandler

文章目录前言参考目录学习笔记1、type 包中类的归类总结2、类型处理器2.1、TypeReference 类3、类型注册表3.1、TypeHandlerRegistry#getTypeHandler前言 本文内容对应的是书本第 8 章的内容,主要是关于类型处理器 TypeHandler 的学习。 这一章节的学习有些地方理…...

建造《流浪地球2》中要毁灭人类的超级量子计算机MOSS的核心量子技术是什么?

1.《流浪地球2》中的量子计算机 2023年中国最火的电影非《流浪地球2》莫属,在《流浪地球2》中有一个人工智能机器人MOSS ,它的前身是“550W”超级量子计算机,“MOSS”是它给自己起的名字(“550W”倒转180度就是“MOSS”&#xff…...

数据结构~七大排序算法(Java实现)

目录 插入排序 直接插入排序 希尔排序 选择排序 直接选择排序 堆排序 交换排序 冒泡排序 快速排序 递归实现 优化版本 归并排序 插入排序 直接插入排序 public class MySort {public static void insertSort(int[] array) {for (int i 1; i < array.length;…...

python练习

项目场景一&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 问题描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶…...

个人网站做捐赠发布违法吗/如何提交百度收录

RBAC-基于角色的访问控制 在Kubernetes中&#xff0c;最佳的做法是&#xff0c;为特定的应用程序的服务帐户授予角色,确保应用程序在指定的范围内运行。要详细了解服务帐户权限请阅读官方Kubernetes文档. Bitnami写了一个在集群中配置RBAC的指导&#xff0c;可让你了解RBAC基础…...

做网站需要备案吗/沈阳网站关键词优化多少钱

一、定位 定位&#xff1a;定位是一种更加高级的布局手段。通过定位可以将元素放在页面的任意位置。position&#xff1a;通过position来设置定位。可选值:static 默认值&#xff0c;元素是静止&#xff0c;没有开启定位relative 开启元素的相对定位absolute 开启元素的绝对…...

网站内页seo查询/搜索引擎优化简称

上一篇文章&#xff1a;【AI绘画】我以Midjourney为主学习AI绘画效果咋样&#xff1f;_山楂山楂丸的博客-CSDN博客 目录 前言 一、「Stable Diffusion」 是什么 二、「Stable Diffusion」上手演练 三、竟然还有ChatGPT&#xff1f; 四、「Stable Diffusion」作品展示 五、…...

做淘客网站需要企业的域名/百度关键词规划师入口

如今&#xff0c;电子书轻便海量的良好移动式体验受到广大年轻读者的喜爱。但是很多人也发现&#xff0c;有些电子书网站很贵&#xff0c;某些书籍还搜不到。今天&#xff0c;就给大家推荐6个电子书网站&#xff0c;不仅免费&#xff0c;而且品类丰富&#xff0c;能帮你找到99%…...

长春比较有名的做网站建设/百度搜索风云榜排名

昨天中午午饭后&#xff0c;和KK去星巴克买咖啡&#xff0c;收银小姐每次都会把客人的姓写在杯子上&#xff0c;这样就不会搞错了。这次小姐又问了&#xff0c;我和KK同时回答&#xff0c;我回答说“康”&#xff0c;她回答说“王”&#xff0c;结果小姐就说“是唐小姐对吗&…...

编写网站 语言/2023年适合小学生的新闻有哪些

我首先是安装lnmp一键包,里面有redis3.1.3的扩展包&#xff0c;cd lnmp1.4/src/redis3.1.3/执行phpize 生成配置&#xff0c;/usr/local/php7.15/bin/phpize然后 ./configure --with-php-config/usr/local/php7.15/bin/php-configmake&&make install查看redis.so文件是…...