Redis(一):简单动态字符串

当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用前面所提到简单动态字符串(SDS)来表示字符串值,比如在Redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的。

举个栗子

set msg “hello world”

对于这条NoSql语句,Redis将在数据库中创建一个新的键值对,其中:

  • 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串值为"msg"的SDS。

  • 键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串"hello world"的SDS

再举个栗子

rpush fruits “apple” “banana” “cherry”

同理,redis将在数据库中创建一个新的键值对,其中:

  • 键值对的键是以恶字符串对象,对象的底层实现是一个保存着字符串值为"fruits"的SDS。

  • 键值对的值就有所不同之前了,这里是一个列表对象,列表对象里面包含了三个字符串对象,这三个字符串底层都是由SDS实现的

除了用来保存数据库中的字符串值之外,SDS有时还会被应用在缓冲区(buffer),比如AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,这些都是由SDS来实现的。

SDS的定义

SDS是一个结构体,每一个sdshdr结构表示一个sds值,如下所示

struct sdshdr{

//len记录buf数组中已使用字节的数量

//也可以理解成SDS所保存字符串的长度

int len;

//free记录buf数组中剩余未使用字节的数量

int free;

//字节数组,用于保存字符串

char buf[]

};

举个栗子

在这里插入图片描述

  • free属性的值为0,表示这个SDS没有分配任何未使用的空间,即buf数组的空间已经用完了

  • len属性的值为5,代表这个SDS储存了一个五字节长的字符串

  • buf属性是一个char类型的数组(记住是一个字节数组),数组的前5个字节分别为’R’,‘E’,‘D’,‘I’,‘S’,最后一个字符则保存了空字符’\0’。

SDS遵循传统字符串的以空字符结尾,保存空字符的一个字节空间是不计算在SDS的len属性里面的,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符相对于使用者来说是完全透明的。遵循这个,可以让SDS重用一部分C字符串函数库里面的函数

SDS与C字符串的区别

C语言使用长度为N+1的字符数组来表示长度为N的字符串,并且字符数组的最后一个元素总是空字符"\0"。

常数复杂度获取字符串长度

C语言的字符串是没有记录自身长度的信息的(只有整个字符数组的长度),所以为了获取一个C字符串的长度,程序是需要进行遍历的(直到遇到空字符,代表结束),所以时间复杂度为 O ( N ) O(N) O(N)。

SDS与C不同,它有一个len属性,里面记录了自身长度信息,所以获取字符串长度只需直接访问len属性就好了,时间复杂度为 O ( 1 ) O(1) O(1) 。

更新和维护len属性都是由SDS的API在执行时自动完成的,使用者在使用SDS时,无需进行任何手动修改长度的工作。

好处

通过使用SDS而不是C字符串,Redis将获取字符串长度所需的时间复杂度从 O ( N ) O(N) O(N)降为了 O ( 1 ) O(1) O(1),这确保了获取字符串长度的工作不会成为Redis的性能瓶颈,例如,对一个非常长的字符串使用STRLEN命令,即如下

strlen key名字 //用来获取字符串长度

这是不会对系统性能造成任何影响的,因为STRLEN命令的时间复杂度仅为 O ( 1 ) O(1) O(1)。

杜绝缓冲区的溢出

不记录自身长度还会带来一个问题就是很容易造成缓冲区溢出,比如实现两个字符串拼接,如果没有为dest(目标)分配足够多的空间,就会产生缓冲区溢出,举个栗子

比如这里往REDIS后面进行添加字符串"CLUSTER",那么就会变成下面这个样子

在这里插入图片描述

在这里插入图片描述

由于没有分配足够的空间,拼接的内容就会溢出到后面的内存中,让MONGODB被修改了。

SDS的空间分配策略

SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性;当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改的条件要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,这样就不会出现缓冲区溢出的问题了。

减少修改字符串时带来的内存重分配次数

首先介绍一下什么是内存重分配,**C语言的字符串并不记录自身的长度,所以对于一个包含了N个字符的C字符串来说,这个C字符串的底层字符数组的长度为N+1(留有一个用来储存空字符串),**正是因为有这种关系,所以,对C字符串进行修改,无论是增加和删除都要进行内存重新分配,否则是无法满足上面所说的关系的。

  • 增长字符串时,进行的是拼接操作,那么在执行这个操作之前,程序会先通过内存重分配来扩展底层数组的空间大小,否则会产生缓冲区溢出

  • 缩短字符串时,进行的是截断操作,那么在执行这个操作之前,程序会先通过内存重分配来释放字符串不再使用的底层数组的空间,否则会产生内存泄漏

因为内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个消耗性能和耗时的操作。

  • 如果程序中对字符串修改的次数不多,一次两次的,那么重分配的消耗可以接受

  • 但是Redis作为数据库,数据是经常被修改的,如果每次修改字符串都进行一次内存重分配的话,是不可以接受的,会对性能造成比较大的影响

空间预分配

空间预分配就是用于优化SDS的字符串增长操作的,当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间

额外分配的空间由以下几条规则来决定

  • 如果对SDS进行修改之后,SDS的长度,也就是len属性,小于1MB,那么程序将会分配和len属性同样大小的未使用空间,也就是说,如果恰巧修改完之后,把整个数组用完了,那么进行预分配,free的大小和len的大小是一样的

  • 如果SDS进行修改之后,SDS的长度,也就是len属性,大于1MB,那么程序会分配1MB的未使用空间。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值