Redis(02)| 数据结构-SDS

一、键值对数据库是怎么实现的?

在开始讲数据结构之前,先给介绍下 Redis 是怎样实现键值对(key-value)数据库的。
Redis 的键值对中的 key 就是字符串对象,而 value 可以是字符串对象,也可以是集合数据类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。
举个例子,我这里列出几种 Redis 新增键值对的命令:

> SET name "mingjing"
OK
> HSET person name "mingjing" age 18
0
> RPUSH stu "mingjing" "huahua"
(integer) 

这些命令代表着:
● 第一条命令:name 是一个字符串键,因为键的值是一个字符串对象;
● 第二条命令:person 是一个哈希表键,因为键的值是一个包含两个键值对的哈希表对象;
● 第三条命令:stu 是一个列表键,因为键的值是一个包含两个元素的列表对象;

1.1 这些键值对是如何保存在 Redis 中的呢?

Redis 是使用了一个「哈希表」保存所有键值对,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对。哈希表其实就是一个数组,数组中的元素叫做哈希桶

1.2 Redis 的哈希桶是怎么保存键值对数据的呢?

哈希桶存放的是指向键值对数据的指针(dictEntry*),这样通过指针就能找到键值对数据,然后因为键值对的值可以保存字符串对象和集合数据类型的对象,所以键值对的数据结构中并不是直接保存值本身,而是保存了 void * keyvoid * value 指针,分别指向了实际的键对象和值对象,这样一来,即使值是集合数据,也可以通过 void * value 指针找到。如下图:
在这里插入图片描述

这些数据结构的内部细节,我先不展开讲,后面在讲哈希表数据结构的时候,在详细的说说,因为用到的数据结构是一样的。这里先大概说下图中涉及到的数据结构的名字和用途:
● redisDb 结构,表示 Redis 数据库的结构,结构体里存放了指向了 dict 结构的指针;
● dict 结构,结构体里存放了 2 个哈希表,正常情况下都是用「哈希表1」,「哈希表2」只有在 rehash 的时候才用,具体什么是 rehash,我在本文的哈希表数据结构会讲;
● ditctht 结构,表示哈希表的结构,结构里存放了哈希表数组,数组中的每个元素都是指向一个哈希表节点结构(dictEntry)的指针;
● dictEntry 结构,表示哈希表节点的结构,结构里存放了 void * keyvoid * value 指针, key 指向的是 String 对象,而 value 则可以指向 String 对象,也可以指向集合类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。
特别说明下,void * keyvoid * value 指针指向的是 Redis 对象,Redis 中的每个对象都由 redisObject 结构表示,如下图:
在这里插入图片描述

对象结构里包含的成员变量:
● type,标识该对象是什么类型的对象(String 对象、 List 对象、Hash 对象、Set 对象和 Zset 对象);
● encoding,标识该对象使用了哪种底层的数据结构;
● ptr,指向底层数据结构的指针。
所以Redis 键值对数据库的全景图如下,你就能清晰知道 Redis 对象和数据结构的关系了:
在这里插入图片描述

接下里,就好好聊一下底层数据结构!

二、SDS简洁

字符串在 Redis 中是很常用的,键值对中的键是字符串类型,值有时也是字符串类型。
Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char 字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS) 的数据结构来表示字符串,也就是 Redis 的 String 数据类型的底层数据结构是 SDS。
既然 Redis 设计了 SDS 结构来表示字符串,肯定是 C 语言的 char 字符数组存在一些缺陷。
要了解这一点,得先来看看 char 字符数组的结构。

2.1 Redis为什么不直接使用C 语言字符串

C 语言的字符串其实就是一个字符数组,即数组中每个元素是字符串中的一个字符。
比如,下图就是字符串“mingjing”的 char 字符数组的结构:
char* name = “mingjing”

mingjing\0

在 C 语言里,对字符串操作时,char * 指针只是指向字符数组的起始位置,而字符数组的结尾位置就用“0”表示,意思是指字符串的结束。
因此,C 语言标准库中的字符串操作函数就通过判断字符是不是 “0” 来决定要不要停止操作,如果当前字符不是 “0” ,说明字符串还没结束,可以继续操作,如果当前字符是 “0” 是则说明字符串结束了,就要停止操作。

C 语言字符串用 “0” 字符作为结尾标记有个缺陷。假设有个字符串中有个 “0” 字符,这时在操作这个字符串时就会提早结束,

因此,除了字符串的末尾之外,字符串里面不能含有 “0” 字符,否则最先被程序读入的 “0” 字符将被误认为是字符串结尾,这个限制使得 C 语言的字符串只能保存文本数据,不能保存像图片、音频、视频文化这样的二进制数据(这也是一个可以改进的地方)
另外, C 语言标准库中字符串的操作函数是很不安全的,对程序员很不友好,稍微一不注意,就会导致缓冲区溢出。
举个例子,strcat 函数是可以将两个字符串拼接在一起。

//将 src 字符串拼接到 dest 字符串后面
char*strcat(char*dest,constchar* src);

C 语言的字符串是不会记录自身的缓冲区大小的,所以 strcat 函数假定程序员在执行这个函数时,已经为 dest 分配了足够多的内存,可以容纳 src 字符串中的所有内容,而一旦这个假定不成立,就会发生缓冲区溢出将可能会造成程序运行终止,(这是一个可以改进的地方)。
而且,strcat 函数和 strlen 函数类似,时间复杂度也很高,也都需要先通过遍历字符串才能得到目标字符串的末尾。然后对于 strcat 函数来说,还要再遍历源字符串才能完成追加,对字符串的操作效率不高。
好了, 通过以上的分析,我们可以得知 C 语言的字符串不足之处以及可以改进的地方:
● 获取字符串长度的时间复杂度为 O(N);
● 字符串的结尾是以 “0” 字符标识,字符串里面不能包含有 “0” 字符,因此不能保存二进制数据;
● 字符串操作函数不高效且不安全,比如有缓冲区溢出的风险,有可能会造成程序运行终止;
Redis 实现的 SDS 的结构就把上面这些问题解决了,接下来我们一起看看 Redis 是如何解决的。

三、 SDS 结构设计

下图就是 Redis 5.0 的 SDS 的数据结构:
在这里插入图片描述

结构中的每个成员变量分别介绍下:
● len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
● alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
● flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
● buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。
总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷。

3.1 O(1)复杂度获取字符串长度

C 语言的字符串长度获取 strlen 函数,需要通过遍历的方式来统计字符串长度,时间复杂度是 O(N)。
而 Redis 的 SDS 结构因为加入了 len 成员变量,那么获取字符串长度的时候,直接返回这个成员变量的值就行,所以复杂度只有 O(1)。

3.2 二进制安全

因为 SDS 不需要用 “0” 字符来标识字符串结尾了,而是有个专门的 len 成员变量来记录长度,所以可存储包含 “0” 的数据。但是 SDS 为了兼容部分 C 语言标准库的函数, SDS 字符串结尾还是会加上 “0” 字符。
因此, SDS 的 API 都是以处理二进制的方式来处理 SDS 存放在 buf[] 里的数据,程序不会对其中的数据做任何限制,数据写入的时候时什么样的,它被读取时就是什么样的。
通过使用二进制安全的 SDS,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,也可以保存任意格式的二进制数据。

3.3 不会发生缓冲区溢出

C 语言的字符串标准库提供的字符串操作函数,大多数(比如 strcat 追加字符串函数)都是不安全的,因为这些函数把缓冲区大小是否满足操作需求的工作交由开发者来保证,程序内部并不会判断缓冲区大小是否足够用,当发生了缓冲区溢出就有可能造成程序异常结束。
所以,Redis 的 SDS 结构里引入了 alloc 和 len 成员变量,这样 SDS API 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用。
而且,当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小,以满足修改所需的大小。
SDS 扩容的规则代码如下:

hisds hi_sdsMakeRoomFor(hisds s,size_t addlen)
{
	......
	// s目前的剩余空间已足够,无需扩展,直接返回
	if(avail >= addlen)
	return s;
	//获取目前s的长度
	    len =hi_sdslen(s);
	    sh =(char*)s -hi_sdsHdrSize(oldtype);
	//扩展之后 s 至少需要的长度
	    newlen =(len + addlen);
	//根据新长度,为s分配新空间所需要的大小
	if(newlen < HI_SDS_MAX_PREALLOC)
	//新长度<HI_SDS_MAX_PREALLOC 则分配所需空间*2的空间
	        newlen *=2;
	else
	//否则,分配长度为目前长度+1MB
	        newlen += HI_SDS_MAX_PREALLOC;
	...
}

● 如果所需的 sds 长度小于 1 MB,那么最后的扩容是按照翻倍扩容来执行的,即 2 倍的newlen
● 如果所需的 sds 长度超过 1 MB,那么最后的扩容长度应该是 newlen + 1MB。
在扩容 SDS 空间之前,SDS API 会优先检查未使用空间是否足够,如果不够的话,API 不仅会为 SDS 分配修改所必须要的空间,还会给 SDS 分配额外的「未使用空间」。
这样的好处是,下次在操作 SDS 时,如果 SDS 空间够的话,API 就会直接使用「未使用空间」,而无须执行内存分配,有效的减少内存分配次数。
所以,使用 SDS 即不需要手动修改 SDS 的空间大小,也不会出现缓冲区溢出的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

哪一个更好?Spring boot还是Node.js

前言 本篇文章有些与众不同&#xff0c;由于我自己手头有些关于这个主题的个人经验&#xff0c;受其启发写出此文。虽然SpringBoot和Node.js服务于很不一样的场景&#xff0c;但是这两个框架共性惊人。其实每种语言都有不计其数的框架&#xff0c;但仅仅一部分是真正卓越的。如…

【优选算法系列】第二节.双指针(202. 快乐数和11. 盛最多水的容器)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;优选算法系列 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff01…

【数据结构】搜索树 与 Java集合框架中的Set,Map

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力…

TSINGSEE青犀睡岗离岗检测算法——确保加油站安全运营

众所周知&#xff0c;加油站是一个需要24小时营业的场所&#xff0c;由于夜间加油人员较少&#xff0c;员工极易处于疲劳或者睡眠状态&#xff0c;为保障安全和效率&#xff0c;通过TSINGSEE青犀睡岗离岗检测算法在加油站场景中&#xff0c;可以及时发现工作人员的疲劳状况&…

JAVA面试题简单整理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、重载和重写的区别一、&和&&的区别一、get和post请求的区别 delete、put一、cookie和session的区别一、Autowired和Resource区别一、”和equals…

笔记:电子设备接地,接的到底是什么地?

电路中有“地”&#xff0c;设备中有“地”&#xff1b;都是“地”&#xff0c;此地非彼地。 混淆的原因 有些混淆&#xff0c;是以为中文翻译造成的&#xff0c;英文所有Ground都统一翻译为“地”&#xff1b; 例1&#xff1a;英文Circuit Ground&#xff0c;应该翻译为电路…

mac安装并使用wireshark

mac安装并使用wireshark 1 介绍 我们在日常开发过程中&#xff0c;遇到了棘手的问题时&#xff0c;免不了查看具体网络请求情况&#xff0c;这个时候就需要用到抓包工具。比较著名的抓包工具就属&#xff1a;wireshark、fildder。我这里主要介绍wireshark。 2 安装 以mac安装为…

Android-宝宝相册(第四次作业)

第四次作业-宝宝相册 题目 用Listview建立宝宝相册&#xff0c;相册内容及图片可自行设定&#xff0c;也可在资料文件中获取。给出模拟器仿真界面及代码截图。 &#xff08;参考例4-8&#xff09; 创建工程项目 创建名为baby的项目工程&#xff0c;最后的工程目录结构如下图所…

自学爬虫—作业1—requests模块

视频&#xff1a; 要求&#xff1a; 肯德基地址查询&#xff0c;爬某个关键字&#xff0c;获取下面的所有page的信息&#xff0c;存到一个json或者txt。 代码&#xff1a; 关键点&#xff0c;&#xff08;1&#xff09;每一个ajax的请求第一个键值对就是所有获得的地址的总数…

【算法|动态规划No.31 | 01背包问题】01背包模板题

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

【Linux】安装与配置虚拟机及虚拟机服务器坏境配置与连接---超详细教学

一&#xff0c;操作系统介绍 1.1.什么是操作系统 操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是一种系统软件&#xff0c;它是计算机硬件和应用软件之间的桥梁。它管理计算机的硬件和软件资源&#xff0c;为应用程序提供接口和服务&#xff0c;并协…

如何使用ffmpeg制作透明背景的视频

最近我们尝试在网页上叠加数字人讲解的功能&#xff0c;发现如果直接在网页上放一个矩形的数字人视频&#xff0c;效果会很差&#xff0c;首先是会遮挡很多画面的内容&#xff0c;其次就是不管使用任何任务背景&#xff0c;画面都和后面的网页不是很协调&#xff0c;如图所示&a…

【操作系统】文件管理大题总结

【操作系统】文件管理大题总结 文章目录 【操作系统】文件管理大题总结前置知识操作系统中的存储单位转换 1、目录管理中的典型问题分析基础例题&#xff1a;往年真题 2、外存的组织方式中的典型问题分析基础例题王道课后题往年真题 3、文件存储空问管理中的典型问题分析基础例…

JS问题:如何实现文本一键复制和长按复制功能?

前端功能问题系列文章&#xff0c;点击上方合集↑ 序言 大家好&#xff0c;我是大澈&#xff01; 本文约2000字&#xff0c;整篇阅读大约需要4分钟。 本文主要内容分三部分&#xff0c;第一部分是需求分析&#xff0c;第二部分是实现步骤&#xff0c;第三部分是问题详解。 …

抓取网页的含义和URL基本构成

抓取网页是指通过爬虫程序从互联网上获取网页的内容和数据。抓取网页是爬虫的核心功能之一&#xff0c;通过抓取网页&#xff0c;可以获取到网页中的文本、图片、链接等信息&#xff0c;用于后续的数据分析、挖掘和应用。 URL&#xff08;Uniform Resource Locator&#xff09…

PHP递归实现无限级分类

什么是无限级分类&#xff1f; 无限级分类是一种对商品或信息进行分类的方式&#xff0c;在这种分类方式中&#xff0c;每个分类都可以再次细分出更多的子分类&#xff0c;形成无限的级别 应用场景&#xff1a; 一个电商网站的分类可以是&#xff1a;服装、鞋类、家居用品等…

保姆级教学安装Linux操作系统,以及Linux的语法入门

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Linux》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专…

人工智能基础_机器学习003_有监督机器学习_sklearn中线性方程和正规方程的计算_使用sklearn解算八元一次方程---人工智能工作笔记0042

然后我们再来看看,如何使用sklearn,来进行正规方程的运算,当然这里 首先要安装sklearn,这里如何安装sklearn就不说了,自己查一下 首先我们还是来计算前面的八元一次方程的解,但是这次我们不用np.linalg.solve这个 解线性方程的方式,也不用 直接 解正规方程的方式: 也就是上面…

基于Kubesphere容器云平台物联网云平台Devops实践

基于Kubesphere容器云平台物联网云平台Devops实践 项目背景 ​ 公司是做工业物联网相关业务的&#xff0c;现业务是云平台&#xff0c;技术栈 后端为 Springboot2.7JDK11 &#xff0c;前端为 Vue3Ts&#xff0c;需要搭建自动化运维平台以实现业务代码自动部署上线&#xff0c;…

【NLP】word复制指定内容到新的word文档

目录 1.python代码 2.结果 需求&#xff1a; 复制word文档里的两个关键字&#xff08;例如“起始位置”到“结束位置”&#xff09;之间的内容到新的word文档。 前提&#xff1a;安装win32包&#xff0c;通过pip install pywin32命令直接安装。话不多说&#xff0c;直接上代码…