在 Redis 里面,C 字符串只会作为字符串字面量用在一些无须对字符串值进行修改的地方,比如打印日志。当 Redis 需要对一个被修改的字符串操作时,Redis 会使用  SDS 来表示字符串值,比如在 Redis 的数据库里面,包含字符串值的键值对在底层都是由 SDS 实现的。例如 redis> set msg "hello world"

一,何谓 SDS

SDS(Simple Dynamic String)在 Redis 中被定义为一个结构

structsdshdr{intlen;// 记录 buf 数组中已使用字节的数量,等于 SDS 所保存字符串的长度intfree;// 记录 buf 数组中未使用字节的数量charbuf[];// 字节数组,用于保存字符串}

SDS 示例

  • free 属性值为0,表示这个 SDS 没有分配任何未使用的空间;

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

  • buf 数值是一个 char 类型的数组,数组前五个字节分别保存了 'R','e','d','i','s' 五个字符,而最后一个字节则保存了一个空字符;

二,SDS 与 C 字符串的区别

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

2.1 获取字符串的长度

C 语言中获取字符串的长度,需要程序遍历整个字符串,直到遇到代表字符串结尾的空字符为止,这个操作的复杂度为O(N);比如,如果想获取字符串 "Redis" 的长度,在 C 中是按照如下过程操作的。

与 C 语言不同的是,SDS 中有一个属性 len,专门用于记录字符串的属性,并且该属性值是由 SDS API 自动计算完成的,我们在使用时只需去读取该值即可。这样复杂度就变为 O(1);比如获取字符串 "hello,world" 的长度只需读取 len 属性即可。

小结:就获取字符串的长度而言, SDS 显然比 C 更具优势。

2.2 杜绝缓冲区溢出

举例,char *strcat(char *dest, const char *src) 是 C 语言中的函数,用于将 src 字符串拼接到 dest 字符串的末尾,因为 C 字符串不记录自身的 长度,所以 strcat 假定用户在执行这个函数时,已经为 dest 分配了足够多的内存,可以容纳 src 字符串中的所有内容,而一旦这个假设不成立时就会产生缓存溢出。假设有两个在内存中紧邻的C字符串 s1 和 s2,其中 s1 保存了字符串 "Redis",而 s2 保存了字符串 "MongoDB",如图所示:

如果用户在未提前分配内存的情况下执行 strcat(s1, " Cluster") ,那么将会导致 s1 的数据溢出到 s2 所在的内存空间中,导致 s2 的数据被修改;

针对上面可能造成的问题,SDS 做了如下修改:当 SDS API 需要对 SDS 进行修改时,API 会先检查 SDS 的空间会否满足所需,不满足情况下 API 会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。这样在不需要用户自己操作的情况下避免了内存溢出的发生。

2.3 操作字符串时的内存分配策略

C 语言中对字符串的增减都需要对这个字符串数组进行内存重新分配。如果是增长字符串的操作,那么在操作之前,需要先通过内存重分配来扩展底层数组的大小,否则就会产生缓冲区溢出。如果是缩短字符串的操作,那么在操作之前,需要先通过内存重新分配来释放不再使用的那部分空间,否则就会产生内存泄漏。因为内存的重新分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作。在一般程序中,如果修改字符串的长度的情况不太常见,那么每次修改就执行一次内存重分配是可以接受的。但是 Redis 作为数据库,数据被频繁修改是很常见的,此时如果每次都执行内存重新分配的话,将会对性能造成不小的影响。为了避免 C 中的上述缺陷,SDS 定义了未使用空间属性。在 SDS 中,buf 数组的长度不一定就是字符数量 + 1,数组里面可以包含未使用的字节,而这些未使用的字节数量就由 SDS 的 free 属性记录。基于该属性 SDS 实现了空间预分配和惰性分配空间释放两种优化策略。

2.3.1. 空间预分配

当 SDS 字符串增长时,SDS 的 API 不仅会为 SDS 分配修改所必须要的空间,还会为 SDS 分配额外的未使用空间。如果对 SDS 进行修改后,SDS 的 len 小于 1MB,那么程序将会分配和 len 属性同样大小的未使用空间即 free = len,buf 的实际长度将是 len + free + 1;如果对 SDS 修改之后的长度大于等于 1MB,那么程序将会分配 1MB 的未使用空间。此时 buf 的实际长度是 len + 1MB + 1byte。通过这种分配策略,SDS 将连续增长 N 次字符串所需要的内存重分配次数从必定 N 次降为 最多 N 次。

2.3.2 惰性空间释放

当 SDS 字符串缩短时,SDS 的 API 不会立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录下来,并等待将来使用。举例,将长度为 11 个字符的字符串 "XYXaYYbcXYY" 中的 "X" 和 "Y" 字符全部去掉;

执行完缩短操作之后的结果如图所示:

可知,SDS 并没有释放因为去掉 "X" 和 "Y" 所多出来的 8 字节空间,而是将这 8 字节空间作为未使用空间保留在了 SDS 里面,如果将来对 SDS 进行增长操作的话,可以直接使用这些未使用空间。为了避免惰性空间释放策略造成的内存浪费,SDS 提供了相应的 API,让我们在需要时通过这些 API 释放未使用的空间。

2.4 二进制安全

C 中字符编码必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,因为 C 语言中默认以 \0 作为字符串的结尾。这使得 C 字符串只能用来保存文本数据,而像图片,音频,视频,压缩文件等这样的二进制数据则不能被保存。而 SDS 中判断字符串是否结束的标志是 len 属性而不是 \0,这使得 Redis 可以存储任何二进制数据。

2.5 总结

C 字符串 SDS
获取字符串长度的复杂度为 O(N) 获取字符串长度的复杂度为 O(1)
API 不安全,可能造成缓存区溢出 API 是安全的,不会造成缓存区溢出
修改 N 次字符串必然需要 N 次内存重新分配 修改 N 次字符串最对需要 N 次内存重新分配
只能保存文本数据 可以保存文本数据或二进制数据
可以使用中的所有函数 可以使用中的部分函数 

2020年10月28日于将台

c++判断一个字符串里面有特殊符号_简单动态字符串(SDS)相关推荐

  1. c语言追加字符串_Redis源码解析二--简单动态字符串

    Redis 简单动态字符串 1.介绍 Redis兼容传统的C语言字符串类型,但没有直接使用C语言的传统的字符串(以'0'结尾的字符数组)表示,而是自己构建了一种名为简单动态字符串(simple dyn ...

  2. redis笔记_源码_简单动态字符串SDS

    参照:https://zcheng.ren/sourcecodeanalysis/theannotatedredissourcesds/#sds%E5%B0%8F%E7%BB%93 这里用char b ...

  3. 【Redis系列2】Redis字符串对象之SDS(简单动态字符串)实现原理分析

    Redis字符串对象之SDS实现原理分析 前言 字符串对象 为什么Redis的字符串对象是二进制安全的 SDS空间分配策略 空间预分配 惰性空间释放 SDS和C语言字符串区别 SDS的底层存储对象 d ...

  4. Redis源码初探(1)简单动态字符串SDS

    前言 现在面试可太卷了,Redis基本是必问的知识点,为了在秋招中卷过其他人(虽然我未必参加秋招),本菜鸡决定从源码层面再次学习Redis,不过鉴于本菜鸡水平有限,且没有c语言基础,本文不会对源码过于 ...

  5. Redis内部数据结构详解之简单动态字符串(sds)

    本文所引用的源码全部来自Redis2.8.2版本. Redis中简单动态字符串sds数据结构与API相关文件是:sds.h, sds.c. 转载请注明,本文出自:http://blog.csdn.ne ...

  6. Redis之简单动态字符串sds

    转载:https://segmentfault.com/a/1190000012262739 redis在处理字符串的时候没有直接使用以'\0'结尾的C语言字符串,而是封装了一下C语言字符串并命名为s ...

  7. redis学习 -- 简单动态字符串

    Redis没有使用C语言字符串的形式,通过'\0'作为结尾,而是使用了简单动态字符串(simple dynamic string). 当Redis使用的字符串不需要修改字符串的内容的时候,可以使用C语 ...

  8. 《Redis设计与实现》阅读笔记(二)--简单动态字符串

    简单动态字符串 Redis只在一些无需对字符串进行修改的地方使用C字符串,大部分时候使用简单动态字符串(simple dynamic string, SDS),字符串的抽象类型.二进制安全,可以存放任 ...

  9. Redis简单动态字符串

    简单动态字符串 Simple Dynamic String是Redis内部自己定义的一种数据类型 在Redis内部, 任何包含字符串的键值对都是由SDS实现的 SDS还被用于缓冲区, 比如AOF缓冲区 ...

最新文章

  1. 文件处理命令:sed
  2. DllMain使用的注意事项
  3. pandas使用bdate_range函数获取起始时间(start)和结束时间(end)范围内的所有工作日日期(business day)
  4. 0.0 环境搭建 - PyTorch学习笔记
  5. HttpWatch是强大的网页数据分析工具
  6. 经典FOXMAIL报错 winsock error 11004
  7. 关于颜色值透明度的设置
  8. Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1 (ISO)
  9. 深度学习第一次课-数学
  10. 2018全球智能手机市场的主要趋势
  11. 计算机组成原理白中英课后习题题答案
  12. 十款好用的PDF编辑软件推荐
  13. 组策略设置计算机计划任务,组策略 运行计划任务 Powershell
  14. 东芝打印机共享怎么设置_东芝2051C打印机怎么连接并扫描文件到电脑?
  15. 我的第一次CTF比赛(SDPC)
  16. C语言 数字实现字母表 链表实现字母表
  17. tracert路由跟踪(ICMP)
  18. 大家都在学C语言吧,作为程序员这有一个问题,秃顶算工伤吗?
  19. 我是没有口袋的哆啦a梦
  20. linux io栈(读写流程)

热门文章

  1. redhat7图形界面网卡设置_Redhat Linux Interprise基本网络配置与调试
  2. android消息,android消息机制
  3. java 汾_Javaweb学习 4
  4. matlab 传递函数求截止频率,高分求解RC滤波电路的传递函数和截止频率
  5. java程序自动重启_java程序自动重启
  6. Kafka安装之一 Zookeeper
  7. 什么是网关,网关的作用
  8. 视频码率[百科词条]
  9. PAT-BASIC-1001-害死人不偿命的(3n+1)猜想
  10. 全文索引 CONTAINS