Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库、缓存和消息中间件。其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 string 类型,在 Redis 中 string 类型运用还是很广泛的。本文主要介绍 string 的数据结构—— 简单动态字符串(Simple Dynamic String) 简称sds。

sds 实现

sds 的数据结构:

struct sdshdr {

     //buf 已占用的长度
int len; // buf 剩余的可用的长度
int free; // 保存字符串数据的地方
char buf[];
}

结构 sdshdr 保存了 len、free 和 buf 三个属性,分别记录字符的已使用的长度,未使用的长度,以及实际保存字符串的数组。

以下是一个新建的,保存 hello world 字符串的 sdshdr 结构:

struct sdshdr {
len = 5;
free = 0;
buf = "hello\0";
}
  • free 属性值为0,表示这个sds没有分配未使用的空间。
  • len 属性值为5,表示这个sds保存了一个五字节长的字符串。
  • buf 属性是一个 char 类型的数组,数组的前五个字节分别保存了 'h'、'e'、'l'、'l'、'o' 五个字符,而最后一个字节保存了空字符'\0'。

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

通过 len 属性,可以实现时间复杂度 O(1) 的长度计算。另外通过对 buf 分配一些额外的空间,并使用 free 记录未使用空间的长度,sdshdr 可以减少内存的重新分配。这是 sds 相对 c 字符串的一个优势。

为何 Redis 不用 C 语言表示字符串

Redis 是使用 C 语言开发的,而在使用最多的字符串上,Redis 没有使用 C 语言传统的字符串表示,而且使用自己构建的简单动态字符串(sds)

在 C 语言中,字符串可以用一个 \0 结尾的 char 数组表示。比如 hello world 在 C 语言中就可以表示为"hello world\0"。数组一般初始化以后长度就已经固定了,不能支持字符串追加append长度计算操作:

  • 每次计算字符串长度都要遍历一遍数组,所以时间复杂度是O(N)
  • 对字符串每次进行追加操作,需要对字符串进行一次内存分配

sds 优化追加字符操作

Redis 作为数据库,对于查询速度要求严格,数据修改也比较频繁,如果每次修改字符串都需要执行一次内存分配的话,都会占用大量的时间。所以 Redis 选择了 sds 而不是 C 字符串,sds 可以减少追加字符的内存分配。通过举例来说明,执行以下操作时,sds 内部的变化:

redis> set msg "hello world"
OK redis> append msg " again"
(integer)18 redis> get msg
"hello world again"

首先 set 命令创建并保存hello world 到一个 sdshdr 中,这个 sdshdr 的值如下:

struct sdshdr {
len = 11;
free = 0;
buf = "hello world\0";
}

当执行 append 命令时,相对应的 sdshdr 被更新,字符串 " again" 会被追加到原来的 "hello world" 之后:

struct sdshdr {
len = 17;
free = 17;
buf = "hello world again\0 ";
}

当调用 set 命令创建 sdshdr 时,Redis 没有给 sdshdr 分配多余的空间,free 属性为0。而在执行 append 操作之后,Redis 为 buf 分配了多于所需空间一倍的大小。

在执行 append 命令之后,保存 "hello world again" 共需要17 + 1 个字节,但是程序为 sdshdr 分配了 17 + 17 + 1 = 35 个字节,而后续如果在对 sdshdr 进行追加操作,只要追加的长度不超过 free 属性值,那么就不需要对 buf 进行内存重分配。

比如执行以后命令并不会引起 buf 的内存重分配,因为新追加的字符串长度小于17:

redis> append msg  " again"
(integer) 23

对应的 sdshdr 结构如下:

struct sdshdr {
len = 23;
free = 11;
buf = "hello world again again\0 ";
}

redis 内存分配可以查看源码 sds.s/sdsMakeRoomFor,sdsMakeRoomFor 函数描述了内存分配的策略,下面的该函数的伪代码:

// sdshdr:追加前的字符
// addlen:追加字符串
sds sdsMakeRoomFor(sdshdr, addlen) { // 多余空间大于追加空间,无序再分配内存,直接返回
if (free >= addlen) return s;
// 计算新字符的长度
newlen = (len+addlen);
// 如果新字符的长度小于 SDS_MAX_PREALLOC,就分配两倍新字符空间
// 如果新字符的长度大于 SDS_MAX_PREALLOC,就分配新字符空间 + SDS_MAX_PREALLOC 空间
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
// 分配内存
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
// 更新 free 属性
newsh.free = newlen - len;
return newsh;
}

而对于字符的缩短操作,Redis 保存缩短后的字符串,此时并不会进行内存重分配,而是使用 free 属性记录缩短的字符长度。

总结

Redis 的 string 类型为何使用sds而不是 C 字符串,因为sds有两点优势:

  • 计算字符长度,C 字符串复杂度O(n),而 sds 复杂度为 O(1)
  • 字符追加操作,C 字符串每次都需要对内存进行重分配,而 sds 每次会进行动态扩容,当添加字符小于空闲字符时,不会对内容进行分配,减少系统等待时间

参考

Redis 设计与实现

如果觉得文章对你有帮助的话,请点个推荐吧!

深入理解Redis 数据结构—简单动态字符串sds的更多相关文章

  1. 【Redis】简单动态字符串SDS

    C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"; // 也可以添加 ...

  2. 深入理解Redis之简单动态字符串

    目录 SDS SDS与C字符串的区别 SDS获取字符串长度复杂度为O(1),C字符串为O(N) SDS杜绝了缓存区溢出 减少修改字符串时带来的内存重分配次数 二进制安全 Redis没有直接使用C语言传 ...

  3. Redis数据结构之简单动态字符串SDS

    Redis的底层数据结构非常多,其中包括SDS.ZipList.SkipList.LinkedList.HashTable.Intset等.如果你对Redis的理解还只停留在get.set的水平的话, ...

  4. 图解Redis之数据结构篇——简单动态字符串SDS

    图解Redis之数据结构篇--简单动态字符串SDS 前言     相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...

  5. redis底层数据结构--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表

    1.动态字符串 redis中使用c语言的字符床存储字面量,默认字符串存储采用自己构建的简单动态字符串SDS(symple dynamic string) redis包含字符串的键值对都是用SDS实现的 ...

  6. Redis底层探秘(一):简单动态字符串(SDS)

    redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redi ...

  7. redis 系列3 数据结构之简单动态字符串 SDS

    一.  SDS概述 Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默 ...

  8. Redis源码解析:01简单动态字符串SDS

    Redis没有直接使用C字符串(以'\0'结尾的字符数组),而是构建了一种名为简单动态字符串( simple  dynamic  string, SDS)的抽象类型,并将SDS用作Redis的默认字符 ...

  9. Redis核心原理-简单动态字符串SDS

    SDS简介 Redis是C语言编写的,但没有使用c语言的字符串结构,而是自己实现了一套简单动态字符串 simple dynamic string 简称SDS,SDS兼容C语言的字符串类型,原理类似Ja ...

随机推荐

  1. C chdir函数

    chdir微软官方文档 在制作小游戏时遇到的问题:图片音频等资源文件太多,和exe同一个目录不方便集中管理 整理方案1 首先创建了一个名为"resource"的文件夹,把程序执行过 ...

  2. JVM详解(五)——运行时数据区-方法区

    一.概述 1.介绍 <Java虚拟机规范>中明确说明:尽管所有的方法区在逻辑上属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩.但对于HotSpot JVM而言,方法 ...

  3. CAM 模板样式表

    视图 模板类型 模板子类型 类型 子类型 刀具类型 刀具子类型 加工工序 mill_planar FACE_MILLING_AREA 100 261     加工工序 mill_planar FACE ...

  4. 用NXOpen.CAM.CAMSetup.CopyObjects复制刻字操作

    复制刻字操作 手动时,报粘贴对象失败: 用代码执行,报内部错误: Dim destinationObject As NXOpen.CAM.CAMObject = CType(NXOpen.Utilit ...

  5. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在Keil MDK下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  6. (五)、Docker 容器数据卷

    1.什么是数据卷 将运用与运行的环境打包形成容器运行 ,运行可以伴随着容器,但是我们对数据的要求希望是持久化的 容器之间希望有可能共享数据 Docker容器产生的数据,如果不通过docker comm ...

  7. 上午小测3 T1 括号序列 && luogu P5658 [CSP/S 2019 D1T2] 括号树 题解

    前 言: 一直很想写这道括号树..毕竟是在去年折磨了我4个小时的题.... 上午小测3 T1 括号序列 前言: 原来这题是个dp啊...这几天出了好几道dp,我都没看出来,我竟然折磨菜. 考试的时候先 ...

  8. Java并发:Condition接口

    Condition 接口与 Lock 配合实现了等待 / 通知模式,这个和 Object 的监视器方法(wait.notify.notifyAll 等方法)一样,都是实现了等待 / 通知模式,但这两者 ...

  9. 转:基于 xilinx vivado 的PCIE ip核设置与例程代码详解

    连接:https://blog.csdn.net/u014586651/article/details/103826967#comments

  10. Oracle 整库备份还原

    http://www.mamicode.com/info-detail-2481866.html sql语句 system用户登陆 查看表空间和存放位置 select t1.name,t2.name ...