【Redis】字符串原理--简单动态字符串SDS

本文深入解析Redis中使用的简单动态字符串(SDS)的原理与特点,包括其如何优化字符串操作的效率与安全性,以及实现二进制安全的方法。

一.SDS定义

  1. free 属性值为0,表示这个SDS没有分配任何未使用空间。
  2. len  属性值为5,表示这个SDS保存了一个5字节长的字符串。
  3. buf  属性是一个char类型数组,数组的前5个字节保存了,'R' 'e' 'd' 'i' 's' 五个字符,最后一个保存空字符串  '\0'。SDS为了遵循C字符串规则,则结尾是以一个字节空字符串  '\0'结尾。这样SDS可以使用C的一些函数,例如 <stdio.h>/printf 函数。

 一下是含有 "未使用空间" 字符串展示: 

   未使用在设计中有特殊的作用,下面会做介绍。

    

二.SDS与C字符串的区别

'R''e''d''i''s''\0'

         C语言使用长度为N+1 的字符出数组来表示长度为N的字符串,并且最后一个元素总是空字符串 '\0',C使用这种简单的字符串表示方式,并不能满足Redis 在数据结构方面安全性,效率性以及功能性方面的要求。下面将详细比较SDS与C的区别

 2.1  获取字符串长度的复杂度

          C字符串:C不记录自身的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,对于遇到的每个字符进行计数,直到遇到空字符串为止。这个操作的复杂度是 O(N)

          SDS字符串:因为本身记录了 len 属性,所以读取长度直接使用API就可以获取到长度数据,不会对系统性能造成任何影响,把C字符串 O(N)的复杂度降低到 O(1)的复杂度。

 2.2 杜绝缓冲区溢出

           C字符串:因为不记录自身长度,极容易造成缓冲区溢出。

            例如我们在使用C的函数 char * strcat(dest,src) 进行字符串拼接末尾的时候,假设我们拼接S1:Redis  S2:MongoDB  两个字符串,已经分配了足够的内存空间,如下:

现在A程序员通过执行 strcat(S1,' Cluster')  把S1修改成'Redis Cluster' 结果你会发现 'Cluster' 溢出到S2的空间里面了

          SDS字符串:SDS与C字符串不同,SDS使用空间分配策略杜绝了发生缓冲区溢出的可能。当SDS API 需要对SDS修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足,API会自动扩展空间至所需大小,然后才执行实际的修改操作。所以使用SDS既不需要手动修改空间大小,也不会出现溢出的情况

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

            C字符串:因为C字符串不记录自身的长度,所以对于一个包含了N个字符串的C字符串来说,这个C字符串的底层实现总是N+1的字符长的数组。因为C字符出的长度和底层的数组的长度之间存在着这种关联,所以在增长或者缩短一个C字符串,程序都要对保存这个C字符串的数组进行一次内存重分配操作。

          SDS字符串:频繁的内存重分配,对性能会造成影响,为了避免这种缺陷,SDS通过未使用空间解除字符串长度和底层数组长度之间的关系。通过未使用空间策略,SDS实现了空间预分配和惰性空间释放两种优化策略。

  2.3.1 空间预分配(优化字符串增长):

  • 如果扩展后的字符串长度小于 1MB

    • new_alloc = alloc * 2
    • 其中,alloc 是当前已分配的空间大小(即当前字符串长度加上空闲空间),new_alloc 是新的分配空间大小。
    • 举例来说,如果当前字符串的总长度(包括空闲空间)为 50 字节,并且需要追加 10 字节,使得总长度变为 60 字节,那么 Redis 会将新分配的空间大小扩展为 100 字节。
  • 如果扩展后的字符串长度大于等于 1MB

    • new_alloc = alloc + n + 1MB
    • 其中,alloc 是当前已分配的空间大小,n 是所需的附加空间大小(即追加的字符串长度),new_alloc 是新的分配空间大小。
    • 举例来说,如果当前字符串的总长度(包括空闲空间)为 1MB,并且需要追加 512KB,使得总长度变为 1.5MB,那么 Redis 会将新分配的空间大小扩展为 2.5MB(即 1MB 当前空间 + 512KB 追加空间 + 1MB 预分配空间)。

 2.3.2 惰性空间释放(优化字符串缩短操作)

          惰性空间释放的基本原理是:当 SDS 字符串的长度减少时,仅仅修改长度字段,而不立即缩减内存分配。这种策略有助于减少内存分配和释放的频率,从而提高性能。   

          在 SDS 的实现中,惰性空间释放主要涉及以下两个字段:

  • len:当前字符串的长度。
  • free:当前字符串剩余的可用空间。

当一个 SDS 字符串缩短时,新的长度会更新到 len 字段,而原来多出来的空间则更新到 free 字段。这些空间不会立即被释放,而是保留在 SDS 结构中,以备将来追加字符串时使用。

2.4 二进制安全

在 Redis 中,SDS(Simple Dynamic String)字符串的一个重要特性是二进制安全。二进制安全意味着 SDS 可以存储任意类型的二进制数据,包括那些包含空字符('\0')的字节序列。这种能力使 SDS 不仅可以用来存储文本数据,还可以用来存储图像、音频、视频等任意二进制数据。      

2.4.1 进制安全的原理

C 语言中的字符串以空字符('\0')作为结束标志,因此不能包含 '\0' 字符。如果一个字符串包含 '\0',C 语言函数如 strlen 将错误地认为字符串在 '\0' 处结束,这会导致截断和数据丢失。

SDS 通过记录字符串长度而不是依赖于 '\0' 结束符来实现二进制安全。SDS 结构中的 len 字段直接存储字符串的长度,因此 SDS 可以包含任意字符,包括 '\0'

2.4.2 进制安全的实现

SDS 的二进制安全通过以下几个方面实现:

  1. 长度字段:SDS 使用 len 字段存储字符串长度,而不是依赖于 '\0' 结束符。因此,SDS 可以处理包含 '\0' 字符的二进制数据。

  2. 内存操作:SDS 的各种操作函数(如创建、追加、复制等)使用 memcpy 等函数进行内存操作,而不是使用 strcpy 等处理 C 字符串的函数。

2.4.3 进制安全的优点

  1. 多用途:由于 SDS 可以处理任意二进制数据,因此不仅可以存储文本数据,还可以存储图像、音频、视频等其他类型的二进制数据。

  2. 高效性:SDS 在进行字符串操作时使用 len 字段而不是 '\0' 结束符,提高了操作的效率。

  3. 安全性:避免了由于 '\0' 字符导致的截断问题,确保了数据的完整性。

2.4.4 使用场景

二进制安全的 SDS 广泛应用于需要存储和处理非文本数据的场景,包括但不限于:

  • 缓存系统:缓存图片、音频、视频等二进制内容。
  • 消息队列:传输包含二进制数据的消息。
  • 数据存储:存储序列化的对象、压缩数据等。

     

序号C字符串SDS
1获取字符串长度的复杂度为 O(N)获取字符串长度的复杂度为 O(1)
2API是不安全的,可能会造成缓冲区溢出API是安全的,不会造成缓冲区溢出
3修改字符串长度N次必然需要执行N次的内存重分配

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

  1.空间预分配

  2.惰性空间释放

4只能保存文本数据可以保存文本或者二进制数据(二进制安全)
5可以使用所有<string.h> 库中的函数可以使用一部分<string.h>库中的函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上善若水-学者至上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值