Redis源码之SDS简单动态字符串

这篇具有很好参考价值的文章主要介绍了Redis源码之SDS简单动态字符串。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Redis 是内存数据库,高效使用内存对 Redis 的实现来说非常重要。

看一下,Redis 中针对字符串结构针对内存使用效率做的设计优化,版本为Redis3.2。

Redis源码之SDS简单动态字符串

 

 

 

一、SDS的结构 

c语言没有string类型,本质是char[]数组;而且c语言数组创建时必须初始化大小,指定类型后就不能改变,并且字符数组的最后一个元素总是空字符 '\0' 。

以下展示了一个值为 "Redis" 的 C 字符串:

Redis源码之SDS简单动态字符串

Redis没有直接使用C语言的字符串方式,而是构建了一种简单动态字符串(Simple dynamic string, SDS)的类型,Redis中的字符串底层都是使用SDS结构进行存储,比如包含字符串的键值对底层都是使用SDS结构实现的。

SDS结构定义在sds.h中

struct sdshdr{


    int len;//SDS保存的字符串长度


    int free;//buf数组中未使用字节数量


    char buf[];//字符数组,保存字符串


}

  

Redis源码之SDS简单动态字符串

最后一个字节保存了空字符'\0',保留了C字符串的规范,使得SDS结构的字符串,可以重用一部分C函数库的函数。

 

二、为什么不使用C字符串

主要是因为C字符串有以下缺点:

  • 获取字符串长度时间复杂度为O(N):C字符串获取长度需遍历整个字符串,遇到'\0'空字符为止。
  • 缓冲区溢出:比如在进行字符串追加操作时,如果没有分配足够的内存,就会造成内存溢出。
  • 内存重分配:每次增长或者截短字符串,程序都要对保存C字符串的数组进行内存重分配操作,而内存重分配涉及复杂的算法,并可能需要执行系统调用,所以它通常比较耗时。
  • 空字符问题:C字符串中间不能保存空格,否则程序遍历是会误认为是字符串的末尾。这一限制导致C字符串只能存储文本数据,不能保存像图片、音视频、压缩文件等二进制数据。

 

三、怎样解决C字符串问题 

 Redis源码之SDS简单动态字符串

1、SDS通过len属性记录了SDS长度,所以获取长度的时间复杂度为O(1),即strlen命令的时间复杂度是O(1)。

 

2、SDS空间分配策略避免了缓冲区溢出:当对SDS进行修改时,会先检查SDS空间是否满足修改,不满足会自动扩展到所需大小,然后才执行修改。

 

3、较少修改字符串时内存重分配次数:SDS中的free记录buf字节数组中未使用的字节。

redis通过free属性实现空间预分配、惰性空间释放两种优化策略。

 

  • 空间预分配:当对SDS进行增长操作时,程序不仅会分配修改所必须得空间,还会为SDS分配额外的未使用空间。通过预分配策略,减少了连续执行字符串增长操作时内存重分配次数。
  • 惰性空间释放:当对SDS进行截短操作时,程序并不会立即回收缩短后多出来的字节所占用的内存,而是使用free属性记录多出来的字节数,以供将来使用。如果将来要对这个SDS进行增长操作,未使用空间可能就派上用场,并且增长操作也不一定会执行内存重分配。

 

SDS结构中的buf字节数组,是二进制安全的,不仅可以保存字符,也可以保存二进制数据。

SDS保留了C字符串的惯例,将数据的末尾设置为空字符'\0',SDS中之所以保留这一规范是可以重用C字符串函数库的一部分函数,例如追加字符串。

 

四、对字符串的进一步优化

Redis string的三种编码:

  • int 存储8个字节的长整型(long,2^63-1 )
  • embstr, embstr格式的SDS (Simple Dynamic String)
  • raw, raw格式的SDS,存储大于44个字节的长字符串

 

int类型就是指的是数字,那么raw、embstr都代表的是字符串有什么异同吗,下面我们分析下。

 

Redis源码之SDS简单动态字符串

 

 

 

图中展示了两者的区别,可以看到embstr将redisObject和SDS保存在连续的64字节空间内,这样可以只需要一次内存分配,而对于raw来说,SDS和redisObject分离,需要两次内存分配,而且占用更多的内存空间。

 

Redis源码之SDS简单动态字符串

 

 

可以看到embstr在3.2+中使用了叫sdshdr8的结构,在该结构下,元数据只需要3个字节,而Redis需要8个字节,所以总共64个字节,减去redisObject(16字节),再减去SDS的原信息,最后的实际内容就变成了44字节和39字节。

 

Redis源码之SDS简单动态字符串

 

 

当字符串小于等于 44 字节时,Redis 就使用了嵌入式字符串的创建方法,以此减少内存分配和内存碎片。

下面这张图展示了 createEmbeddedStringObject 创建嵌入式字符串的过程:

 Redis源码之SDS简单动态字符串

 

 

总之,只要记住,Redis 会通过设计实现一块连续的内存空间,把 redisObject 结构体和 SDS 结构体紧凑地放置在一起。

这样一来,对于不超过 44 字节的字符串来说,就可以避免内存碎片和两次内存分配的开销了。

 

参考资料:

https://blog.wangjunfeng.com/post/redis-sds/#redis-32%e7%89%88%e6%9c%ac%e5%90%8e%e7%9a%84sds文章来源地址https://www.toymoban.com/news/detail-410746.html

到了这里,关于Redis源码之SDS简单动态字符串的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从源码中分析SDS相较于C字符串的优势

    从本篇文章开始会持续更新有关\\\"redis数据结构源码\\\"的分析。[分析的源码是redis7.2.2版本的,有时候会结合之前的版本]。由于能力有限,有些地方可能有些错误,还望指正。 在分析\\\"intset\\\"的源码的发现。在7.2.2版本,数据类型\\\"set\\\"的底层编码多了一种\\\"listpack\\\",但是之前的版本并

    2024年01月16日
    浏览(40)
  • .NET字符串内存管理:常量字符串、动态创建和字符串池的巧妙结合

      在 .NET 中,字符串是不可变的,这意味着一旦创建,字符串的内容就不能被修改。字符串在内存中以不同的方式存储,具体取决于它是常量字符串还是动态创建的字符串。 常量字符串在编译时就被解析,并在程序的元数据(Metadata)中存储。多个相同的字符串常量可能会共

    2024年01月20日
    浏览(51)
  • BM69 #把数字翻译成字符串# 动态规划 + 逆序遍历(简单易懂!)

    一款好用的文件名批量更改软件ReNamer下载分享 对于一些朋友来说,如果日常处理的文件少,只需要重命名几个文件的话,其实按照常规方法单独处理没什么问题。但是如果需要处理的文件很多,比如几十几百张图片或者文件需要   题解-队列 | #围圈报数# #include iostream#inclu

    2024年03月18日
    浏览(63)
  • 动态规划(一) 变态青蛙跳台阶、最大连续子数组和、字符串分割 附源码讲解

    实现 import java.util.*; public class Main { public static void main(String[] args) { Scanner scan = new Scanner(System.in); while (scan.hasNext()){ int length = scan.nextInt(); int[] array = new int[length]; for (int i = 0; i length; i++) { array[i] = scan.nextInt(); } System.out.println(findResult(array)); } } private static int findResult(int[] array) {

    2024年04月17日
    浏览(55)
  • 【动态规划】【字符串】扰乱字符串

    视频算法专题 动态规划汇总 字符串 使用下面描述的算法可以扰乱字符串 s 得到字符串 t : 如果字符串的长度为 1 ,算法停止 如果字符串的长度 1 ,执行下述步骤: 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 s ,则可以将其分成两个子字符

    2024年02月03日
    浏览(60)
  • 【C语言】字符函数,字符串函数,内存函数

    大家好!今天我们来学习C语言中的字符函数,字符串函数和内存函数。 目录 1. 字符函数 1.1 字符分类函数 1.2 字符转换函数 1.2.1 tolower(将大写字母转化为小写字母) 1.2.2 toupper(将小写字母转化为大写字母) 2. 字符串函数 2.1 字符串输入函数 2.1.1 gets() ​2.1.2 fgets() 2.2 字符串

    2024年02月10日
    浏览(60)
  • 字符串和内存函数(2)

    2.13 memcpy void* memcpy(void* destination, const void* source, size_t num); 函数memcpy从source的位置开始向后复制num个 字节 的数据到destination的内存位置。 这个函数在遇到 ‘\\0’ 的时候并不会停下来。 如果source和destination有任何的重叠,复制的结果都是未定义的。 memcpy是内存拷贝,它可以拷

    2024年02月04日
    浏览(38)
  • 字符串函数+内存函数(详解)

    本期带大家一起来学习字符串函数+内存函数😀 😃 😄 1. 求字符串长度 strlen🚀🚀 字符串 已经 ‘\\0’ 作为结束标志 ,strlen函数返回的是在字符串中 ‘\\0’ 前面出现的字符个数(不包含 ‘\\0’ )。 strlen在库里面的参数是如此的 ⌛️⌛️ 接下来我们来模拟实现strlen ,接下来

    2023年04月17日
    浏览(43)
  • <C语言> 字符串内存函数

    C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串 或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数. 注意:字符串函数都需要包含头文件string.h strlen() 函数用于计算字符串的长度,即字符串中字符

    2024年02月15日
    浏览(47)
  • 字符串函数和内存操作函数

    目录 0.字符串回顾 1.函数介绍 1.1 strlen 1.2 strcpy 1.3 strcat 1.4 strcmp 1.5 strncpy 1.6 strncat 1.7 strncmp 1.8 strstr 1.9 strtok 1.10 strerror 1.11 memcpy 1.12 memmove 1.13 memcmp 1.14 memset 1.15 字符相关函数 字符分类函数 字符转换函数 2.函数模拟实现 2.1模拟实现strlen 2.2模拟实现strcpy 2.3模拟实现strcat 2.4模拟

    2024年02月15日
    浏览(107)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包