前提知识

我们先从百科上摘下Redis的解释:

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、分布式、可选持久性的键值对存储数据库。

(不用过多在意ANSI,它只是一个标准,你可以理解为早期民间版本很多,后来统一了标准,大学课程里包括现在在用的都是标准化后的C语言版本)

没错!Redis的底层是由 C语言 实现的!大学不管是什么专业应该都有这个课,但是不管大家还有没有它的记忆,都不影响我们接下来的学习哈哈哈~

redis第一步,字符串是基础

回想当初学习Java,第一个学习的数据类型应该是基本类型,但redis里最基础的结构其实是字符串,几乎哪里都有它。在mysql定义字段类型时,个别老哥会很中意varchar这个类型,因为万物皆可varchar(不讨论性能的情况下),其实redis就有点这个意思,很多结构的基础都是字符串。

上面提到Redis的底层是由C语言实现的,那岂不是直接拿C语言的字符串(以空字符结尾的字符数组,以下简称C字符串)来用就行?

我先回答了:不行,因为C语言作为早期的编程语言,C字符串是有些“不足”或者说是需要补充的,尤其是像redis这样对速度要求严苛,字符串可能会被频繁修改的服务,于是redis在C字符串的基础上,自己构造了名为 简单动态字符串(simple dynamic string,SDS)的抽象类型,用作redis自己的默认字符串。

(redis也不是就不用C字符串了,只会运用在一些无需对字符串值做修改的地方,例如打印日志时)

C字符串简单介绍,嗯,简单。

首先上图就是一个值为"yikun"的C字符串。

不了解的小伙伴可能不知道为什么后面多了个'\0',其实这个字符是空字符,也可以理解为C语言的“字符串终止符”。

长度为N的字符串,会用长度为N+1的字符数组来表示,最后多出来的1长度就是专门用来存储空符'\0'的。

然后没了。

C语言的字符串就是这么简单。

不急,我们继续往后说~

那么C字符串有哪些“不足”呢?

俗话说得好:知己知彼,百战百胜。

我们得知道C语言的“不足”,才能知道redis为了弥补这种情况,在SDS中做了什么措施。

1.C字符串并没有记录自身长度。

2.会根据空字符'\0'来判断字符串是否结束。

3.只能根据空字符'\0'来判断字符串是否结束。

2和3好像差不多,但意思其实是有细微差别的。

(果然中华文化博大精深~)

C字符串因为上面的“不足”会引起什么问题?

1.获取字符串长度复杂度高

因为 不足1 和 不足3,所以C字符串只能通过遍历字符来获取长度。

我们这里用Java的for循环(只是用Java举例,实际是C语言实现的)简单说明下C语言如何获取字符串长度。

可见字符串越长,这个操作就越耗时,复杂度为O(N)。

2.缓冲区溢出 和 内存泄露

因为 不足2,C字符串可能会在修改字符串时出现问题,因为大家知道内存是连续的,假如我要在字符串后面拼接新的内容,我首先要通过 内存重分配 来扩展底层数组的空间大小。

比如我要在字符串'yikun'后拼接'lucky',但现在后面紧挨存储的是'happy'字符串,所以我首先要扩展数组才能拼接'lucky',不然就把'happy'给覆盖掉,造成缓存区溢出了。

那如果我要缩短'yikun',改成'yi',倒是不用扩展数组。

但我空的这部分又造成内存泄漏,所以还是需要进行 内存重分配。

而 内存重分配 是个十分耗时的操作,如果上面的字符串修改频繁发生的话,对于性能的影响就会很大。

3.没办法存储二进制数据

因为 不足3,假如我们的数据本身就包含空字符串'\0',而代码逻辑是铁面无私的,会认为是字符串终止的意思;而像图片、音频、视频、压缩文件这样的二进制数据是会包含空字符串'\0'的,所以可想而知,数据会出现问题,可能只能识别到前面的部分数据,而丢失后面的内容。

所以针对上面的问题,redis的SDS的结构是怎么设计的?

废话不多说,我们直接来看下SDS的结构:

直观来看,SDS多维护了 free 属性和 len 属性。

len 属性用来记录buf数组中已使用字节的数量,同时也等于SDS所保存字符串的长度。

而属性 free 是用来记录buf数组中未使用字节的数量。

SDS是如何依靠引入两个属性值len、free来解决具体问题呢?

1.获取字符串长度

现在可以直接访问 len 属性来获取字符串长度,就好比Java属性的get方法,复杂度由原来的O(N)一下子降到了O(1)。

而且这个属性是SDS在执行操作时自动完成的,我们无需进行任何维护。

2.空间预分配 和 惰性空间释放

上面我们说内存重分配操作耗时,所以在需要对SDS进行空间扩展的时候,会分配额外的未使用空间。

下面是额外分配空间数量的公式:

1.如果SDS修改之后,SDS的长度(len属性的值)小于1MB,那么就会分配和len属性同样大的未使用空间,也就是free属性会和len属性相同,相当于将原数组长度double。

2.如果SDS修改之后,SDS的长度(len属性的值)大于或等于1MB,那么就会分配固定的1MB未使用空间。

而free属性就决定了是不是需要进行额外的内存重分配操作,假如free为7,而你需要拼接的字符串长度只有5,那就不需要进行内存重分配操作,直接存储就可以。

free:太好了,我这空座就是给老弟你留的,快坐快坐~

所以针对拼接操作来说,本来N次 一定需要N次 的内存重分配操作,现在 最多只需要N次。

(比如你第一次拼接后,free就大于后续要拼接的字符串长度之和了,那其实就只有1次内存重分配操作,所以说最多N次)

缩短操作也是同理,删掉N个字符后,我free就增加N,我先不做内存重分配操作,就先给你留着呗,万一你后面又做拼接是不是。

所以总结:拼接时我们做 空间预分配,缩短时我们做 惰性空间释放,都是为了 减少内存重分配操作。

(SDS其实也提供了响应的方法,在有需要时,可以真正地释放SDS的未使用空间,所以不用担心未使用空间会造成内存浪费。)

3.存储二进制数据

C字符串不能存储二进制数据的原因是只能根据'\0'来判断数据是否结束,不能保证其完整性,但因为SDS的 len 属性,无论你数据里有多少'\0'都没关系,我是根据 len 属性值来判断数据长度的,必定是完整的,所以SDS可以安全地存储二进制数据。

SDS你都有len了,那为啥还要跟C字符串一样在字符串后面加'\0'?

这个很好回答,因为SDS保持和C字符串一样的特性,就不用专门编写多余的函数了,在一定的情况下可以直接用C语言的函数,避免了不必要的重写。

最后的总结

C字符串和SDS之间的区别:

C字符串

SDS

获取字符串长度复杂度为O(N)

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

修改字符串时需要执行N次内存重分配

修改字符串时最多需要执行N次内存重分配

不能保存二进制数据

可以保存二进制数据

可以使用C字符串函数库的所有函数

可以使用C字符串函数库的一部分函数

写在最后的最后

我是苏易困,大家也可以叫我易困,一名Java开发界的小学生,文章可能不是很优质,但一定会很用心。

呜呜呜~ 真是边整理便有收获,自己学习的动力也增长了起来,还真是那句话:你自己明白,才能跟其他人说明白(也不知道是不是说明白了),而且写的时候也会考虑一些取舍,比如有的东西需不需要标注?有的地方是不是过于啰嗦了?是不是写的太入门了一点?可能这些知识在一些人看来都是很基础很入门甚至不用讲的东西。

但怎么说呢,我的初心其实也就是整理和梳理自己的知识,如果能帮到你,我会很开心;如果你觉得不够水平,我也会虚心接受;人嘛,总要有个学习的过程,我也会一直努力的~ 大家一起加油~

Redis数据结构详解(1)-redis中的字符串(SDS)的更多相关文章

  1. 5种Redis数据结构详解

    本文主要和大家分享 5种Redis数据结构详解,希望文中的案例和代码,能帮助到大家. 转载链接:https://www.php.cn/php-weizijiaocheng-388126.html 2. ...

  2. Redis数据结构详解之List(二)

    序言 思来想去感觉redis中的list没什么好写的,如果单写几个命令的操作过于乏味,所以本篇最后我会根据redis中list数据类型的特殊属性,同时对比成熟的消息队列产品rabbitmq,使用red ...

  3. Redis数据结构详解,五种数据结构分分钟掌握

    redis数据类型分为:字符串类型.散列类型.列表类型.集合类型.有序集合类型.redis这么火,它运行有多块?一台普通的笔记本电脑,可以在1秒钟内完成十万次的读写操作.原子操作:最小的操作单位,不能 ...

  4. redis 数据类型详解 以及 redis适用场景场合

    1.  MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的 ...

  5. 【Redis】Redis事务详解,Redis事务支持回滚(不支持悲观锁)

    1.redis事物参考:https://baijiahao.baidu.com/s?id=1613631210471699441&wfr=spider&for=pc (php操作red ...

  6. redis 数据类型详解 以及 redis适用场景场合(滴滴)

    滴滴的面试官问了个问题关于redis的: 我现在想服务器每分钟接收一个用户的请求小于60个,如何处理: 答:使用Redis 缓存服务器,可以设置key=用户ID value不停地加一到了60就停止,然 ...

  7. Redis数据结构详解(2)-redis中的字典dict

    前提知识 字典,又被称为符号表(symbol table)或映射(map),其实简单地可以理解为键值对key-value. 比如Java的常见集合类HashMap,就是用来存储键值对的. 字典中的键( ...

  8. redis数据结构详解之Hash(四)

    序言 Hash数据结构累似c#中的dictionary,大家对数组应该比较了解,数组是通过索引快速定位到指定元素的,无论是访问数组的第一个元素还是最后一个元素,所耗费的时间都是一样的,但是数组中的索引 ...

  9. Redis数据结构详解之Zset(五)

    序言 Zset跟Set之间可以有并集运算,因为他们存储的数据字符串集合,不能有一样的成员出现在一个zset中,但是为什么有了set还要有zset呢?zset叫做有序集合,而set是无序的,zset怎么 ...

随机推荐

  1. Solution -「LOCAL」ZB 平衡树

    \(\mathcal{Description}\)   OurOJ.   维护一列二元组 \((a,b)\),给定初始 \(n\) 个元素,接下来 \(m\) 次操作: 在某个位置插入一个二元组: 翻 ...

  2. Idea个人配置

    Intellij IDEA配置 1. 优化导包配置 2. 取消tab页单行显示 多行显示更多的文件,方便查看. 3. 双斜杠注释改成紧跟代码头 4. 选中复制整行 原本只会复制你选中的代码,改完配置后 ...

  3. 我来教你如何将cpu使用率up起来(shell脚本[含注释])

    这个脚本是为了逃过一些资源检测的,当一些机器当前使用率偏低,会被客户要求收回,那咋办呢?使用下面的脚本,就可以留住你的机器了 假设要求cpu使用率不能低于35% 使用方法:bash up_up_up. ...

  4. 一键生成的BI智能数据看板谁不爱?

    随着互联网思维的深化,如财务.市场.运营.销售等越来越多的岗位,都开始重视并自发性的开始了解并学习数据分析,来引导帮助决策. 人力资源制定效能仪表盘,去实时掌握人员状况和人均效能,通过对招聘漏斗的分析 ...

  5. 如何利用Smartbi做数据分析:2018内5月热销乘用车分析报告

    在2018年第一季度热销乘用车分析报告中,SUV以总体销量15.4%的同比增长率让人不可小觑,Smartbi刚得到5月分析的数据就迫不及待的来看看是否热度不减,结果在5月这个所谓汽车销售淡季,轿车以9 ...

  6. python基础之数值类型与序列类型

    Hello大家好,我是python学习者小杨同学,已经学习python有一段时间,今天将之前学习过的内容整理一番,在这与大家分享与交流,现在开始我们的python基础知识之旅吧. 数值类型与序列类型 ...

  7. 【C# IO 操作 】内存之RAM、SRAM、DRAM、ROM、FLASH、SDRAM、DDR*

    内存 内存(Memory)指的是内存存储器,又称为主存,是CPU用来直接寻址和存储的空间,它相当于一座桥梁,用以负责诸如硬盘.主板.显卡等硬件上的数据与处理器之间数据交换处理,我们可以把内存看作数据缓 ...

  8. 渗透测试中dns log的使用

    转至:https://www.cnblogs.com/rnss/p/11320305.html 一.预备知识 dns(域名解析): 域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问 ...

  9. 多个n维向量围成的n维体积的大小

    前言 上周我们数学老师给了我们一道题,大意就是两个向量a和b,一个点M=$x*a+y*b$,x,y有范围,然后所有M组成的面积是一个定值,求x+y的最小值.当然这是道小水题,但我在想,如果把两个向量变 ...

  10. nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed (2: No such file or directory)

    问题场景 服务器重启后,重启nginx时报错nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed (2: N ...