一.简单动态字符串(SDS)

  Redis中字符串实现有两种方式,C语言传统字符串(以空字符结尾的字符数组)和简单动态字符串(SDS),并将SDS作为默认字符串表示.

  C字符串只会作为字符串字面量,用在一些无需对字符串值进行修改的地方,比如打印日志:

redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");

二.SDS的实现

每个 sds.h/sdshdr 结构表示一个SDS值:

struct sdshdr {

    // 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len; // 记录 buf 数组中未使用字节的数量
int free; // 字节数组,用于保存字符串
char buf[]; };

图 2-1 展示了一个 SDS 示例:

  • free 属性的值为 0 , 表示这个 SDS 没有分配任何未使用空间。
  • len 属性的值为 5 , 表示这个 SDS 保存了一个五字节长的字符串。
  • buf 属性是一个 char 类型的数组, 数组的前五个字节分别保存了 'R' 、 'e' 、 'd' 、 'i' 、 's' 五个字符, 而最后一个字节则保存了空字符 '\0' 。

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

  遵循空字符结尾这一惯例的好处是, SDS 可以直接重用一部分 C 字符串函数库里面的函数。

三.SDS与C字符串的区别

  c字符串是由长度为n+1的字符串数组实现的,并且数组的最后一个值总是为空字符'\0'.

比如说, 图 2-3 就展示了一个值为 "Redis" 的 C 字符串:

1.SDS可以更快的获取字符串长度

  因为c字符串的数据结构为数组,所以也继承了数组的基本特性,比如获取该字符串的长度,需要去遍历整个数组,该操作的复杂度为O(N).

  而SDS本身就维护了len属性记录了字符串的长度,所以获取SDS字符串长度的操作复杂度为O(1).

2.杜绝缓冲区溢出

  c字符串容易造成缓冲区溢出,因为c字符串本身不记录自身长度,比如<string.h>/strcat 函数拼接字符串时,可能导致内存空间不足.

  SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当 SDS API 需要对 SDS 进行修改时,API 会先检查 SDS 的空间是否满足修改所需的要求,如果不满足的话,API 会自动将 SDS 的空间扩展至执行修改所需的大, 然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出问题.

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

  因为 C 字符串并不记录自身的长, 所以对于一个包含了 N 个字符的 C 字符串来说,这个 C 字符串的底层实现总是一个 N+1 个字符长的数组.在对字符串的增长或者缩短操作中,很容易造出内存溢出和内存泄漏.

  为了避免 C 字符串的这种缺陷,SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联:在 SDS 中,buf 数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由 SDS 的 free 属性记录.通过未使用空间,SDS 实现了空间预分配和惰性空间释放两种优化策.

4.空间预分配

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

5.惰性空间释放

  惰性空间释放用于优化 SDS 的字符串缩短操作:当 SDS 的 API 需要缩短 SDS 保存的字符串, 程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录起, 并等待将来使用.

6.二进制安全

  C 字符串中的字符必须符合某种编码(比如 ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据.

  为了确保 Redis 可以适用于各种不同的使用场景,SDS 的 API 都是二进制安全的(binary-safe): 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的,它被读取时就是什么.这也是我们将 SDS 的 buf 属性称为字节数组的原因 —— Redis 不是用这个数组来保存字符, 而是用它来保存一系列二进制数据.比如, 使用 SDS 来保存之前提到的特殊数据格式就没有任何问题,因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束.

7.兼容部分c字符串函数

  虽然 SDS 的 API 都是二进制安全的,但它们一样遵循 C 字符串以空字符结尾的惯例:这些 API 总会将 SDS 保存的数据的末尾设置为空字符,并且总会在为 buf 数组分配空间时多分配一个字节来容纳这个空字符,这是为了让那些保存文本数据的 SDS 可以重用一部分 <string.h>库定义的函数.

总结区别:

C 字符串 SDS
获取字符串长度的复杂度为  。 获取字符串长度的复杂度为  。
API 是不安全的,可能会造成缓冲区溢出。 API 是安全的,不会造成缓冲区溢出。
修改字符串长度 N 次必然需要执行 N 次内存重分配。 修改字符串长度 N 次最多需要执行 N 次内存重分配。
只能保存文本数据。 可以保存文本或者二进制数据。
可以使用所有 <string.h> 库中的函数。 可以使用一部分 <string.h> 库中的函数。

深入了解Redis(1)-字符串底层实现的更多相关文章

  1. Redis的字符串底层是啥?为了速度和安全做了啥?

    面试场景 面试官:Redis有哪些数据类型? 我:String,List,set,zset,hash 面试官:没了? 我:哦哦哦,还有HyperLogLog,bitMap,GeoHash,BloomF ...

  2. Redis 数据结构-字符串源码分析

    相关文章 Redis 初探-安装与使用 Redis常用指令 本文将从以下几个部分进行介绍 1.前言 2.常用命令 3.字符串结构 4.字符串实现 5.命令是如果操作字符串的 前言 平时在使用 Redi ...

  3. redis之字符串命令源代码解析(二)

    形象化设计模式实战             HELLO!架构                     redis命令源代码解析 在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对 ...

  4. 高性能的Redis之对象底层实现原理详解

    对象 在前面的数个章节里, 我们陆续介绍了 Redis 用到的所有主要数据结构, 比如简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合, 等等. Redis 并没有直接使用这些数据结构来实 ...

  5. Redis操作字符串工具类封装,Redis工具类封装

    Redis操作字符串工具类封装,Redis工具类封装 >>>>>>>>>>>>>>>>>>& ...

  6. redis数据类型-字符串类型

    Redis数据类型 字符串类型 字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据.你可以用其存储用户的邮箱.JSON化的对象甚至是一张图片.一个字符串类型键允许存储的 ...

  7. 【Redis面试题】Redis的字符串是怎么实现的?

    年前本人在找工作面试时在Redis相关问题上可栽了跟头.在面试前按常规套路准备了一下,比如 Redis 的常用5种数据结构,Redis持久化策略,Redis实现分布式锁,简单发布订阅等等都准备了,当时 ...

  8. Redis 操作字符串数据

    Redis 操作字符串数据: > set name "Tom" // set 用于添加 key/value 数据,如果 key 存在则覆盖 OK > setnx nam ...

  9. 第二百九十五节,python操作redis缓存-字符串类型

    python操作redis缓存-字符串类型 首先要安装redis-py模块 python连接redis方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...

随机推荐

  1. 权益质押(Staking):这是关于什么的?

    我们最近发表了三篇讲述Fetch.AI分类帐本的文章.这些概述: 我们的权益质押计划的主要特点和验证器的作用 影响我们设计权益质押模型设计的因素 我们的创新共识协议将如何改变用户体验 这些文章包含大量 ...

  2. ASP.NET MVC Route详解

    在MVC3.0版本的时候,微软终于引入了第二种模板引擎:Razor.在这之前,我们一直在使用WebForm时代沿留下来的ASPX引擎或者第三方的NVelocity模板引擎.Razor在减少代码冗余.增 ...

  3. day05 程序与用户交互和基本运算符

    程序与用户交互和基本运算符 目录 程序与用户交互和基本运算符 1.程序与用户交互 1.1什么是与用户交互 1.2为什么要与用户交互 1.3如何与用户交互 1.3.1格式化输出 2基本运算符 2.1算数 ...

  4. XSS 跨站脚本

    xss常用绕过姿势 大小写绕过 利用语句: http://192.168.1.102/xss/example2.php?name=<sCript>alert("hey!" ...

  5. 数据可视化之powerBI入门(七)数据清洗中最常使用的十三招

    https://mp.weixin.qq.com/s?__biz=MzA4MzQwMjY4MA==&mid=2484067158&idx=1&sn=4ad955112df2f4 ...

  6. Linux08 /Docker

    Linux08 /Docker 目录 Linux08 /Docker 1. docker简介/安装 2. Docker镜像加速器的设置 3. 核心三要素 镜像仓库/Registry 镜像/Image: ...

  7. CSS 三大特性 层叠 继承 优先级

    css三大特性 层叠性: 如果一个属性通过两个相同选择器设置到同一个元素上,相同的属性就会出现冲突,那么这个时候一个属性就会将另一个属性层叠掉,采用的是就近原则 继承性: 子标签会继承父标签的某些样式 ...

  8. 02-Python运算符

    一.简介 以10 - 5为例,‘10 - 5’叫做表达式,表达式可以分解成运算符和操作数.整数10和5被称为操作数.‘-’称为运算符. 二.算术运算符 运算符 描述 示例 结果 + 加 - 两个对象相 ...

  9. Ethical Hacking - GAINING ACCESS(19)

    Client-Side Attacks - Social Engineering Tool: The FAT RAT Just like Veil, it generates Undetectable ...

  10. Python Ethical Hacking - MODIFYING DATA IN HTTP LAYER(1)

    MODIFYING DATA IN HTTP LAYER Edit requests/responses. Replace download requests. Inject code(html/Ja ...