Redis 简单动态字符串

1.介绍

Redis兼容传统的C语言字符串类型,但没有直接使用C语言的传统的字符串(以’0’结尾的字符数组)表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的对象。简单动态字符串在Redis数据库中应用很广泛,例如:键值对在底层就是由SDS实现的。

在redis种,有一种数据类型叫string类型,而string类型简单的说就是SDS实现的(简单理解),先通过几个命令来感受一下string类型

127.0.0.1:6379> SET str1 Redis //设置key:value = str1:RedisOK127.0.0.1:6379> GET str1 //获取str1的value"Redis"127.0.0.1:6379> TYPE str1 //获取key的存储类型 string类型string127.0.0.1:6379> STRLEN str1 //str1的长度为5字节(integer) 5

2.SDS的定义

SDS定义在redis源码根目录下的sds.h/sdshdr

typedef char *sds;

//sds兼容传统C风格字符串,所以起了个别名叫sds,并且可以存放sdshdr结构buf成员的地址

SDS也有一个表头(header)用来存放sds的信息。

struct sdshdr { int len; //buf中已占用空间的长度 int free; //buf中剩余可用空间的长度 char buf[]; //初始化sds分配的数据空间,而且是柔性数组(Flexible array member)};

根据这个结构体,我们用图大概表示一下str1,如下图:

len为5,表示这个sds长度为5字节。

free为2,表示这个sds还有2个字节未使用的空间。

buf是一个char[]的数组,分配了(len+1+free)个字节的长度,前len个字节保存着’R’、’e’、’d’、’i’、’s’这5个字符,接下来的1个字节保存着’0’,剩下的free个字节未使用。

3. SDS的优点

SDS本质上就是char *,因为有了表头sdshdr结构的存在,所以SDS比传统C字符串在某些方面更加优秀,并且能够兼容传统C字符串。

3.1 兼容C的部分函数

因为SDS兼容传统的C字符串,采用以’0’作为结尾,所以SDS就能够使用一部分

3.2 二进制安全(Binary Safe)

因为传统C字符串符合ASCII编码,这种编码的操作的特点就是:遇零则止 。即,当读一个字符串时,只要遇到’0’结尾,就认为到达末尾,就忽略’0’结尾以后的所有字符。因此,如果传统字符串保存图片,视频等二进制文件,操作文件时就被截断了。

而SDS表头的buf被定义为字节数组,因为判断是否到达字符串结尾的依据则是表头的len成员,这意味着它可以存放任何二进制的数据和文本数据,包括’0’,如下图:

3.3 获得字符串长度的操作复杂度为O(1)

传统的C字符串获得长度时的做法:遍历字符串的长度,遇零则止,复杂度为O(n)。

而SDS表头的len成员就保存着字符串长度,所以获得字符串长度的操作复杂度为O(1)。

3.4 杜绝缓冲区溢出

因为SDS表头的free成员记录着buf字符数组中未使用空间的字节数,所以,在进行APPEND命令向字符串后追加字符串时,如果不够用会先进行内存扩展,在进行追加。

总之,正是因为表头的存在,使得redis的字符串有这么多优点。

4. SDS源码剖析

4.1 SDS内存分配策略—空间预分配

空间预分配策略用于优化SDS的字符串增长操作。

如果对SDS进行修改后,SDS表头的len成员小于1MB,那么就会分配和len长度相同的未使用空间。free和len成员大小相等。

如果对SDS进行修改后,SDS的长度大于等于1MB,那么就会分配1MB的未使用空间。

通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数。

源代码如下:

sds sdsMakeRoomFor(sds s, size_t addlen) { //对 sds 中 buf 的长度进行扩展 struct sdshdr *sh, *newsh; size_t free = sdsavail(s); //获得s的未使用空间长度 size_t len, newlen; //free的长度够用不用扩展直接返回 if (free >= addlen) return s;  //free长度不够用,需要扩展 len = sdslen(s); //获得s字符串的长度 sh = (void*) (s-(sizeof(struct sdshdr))); //获取表头地址 newlen = (len+addlen); //扩展后的新长度 //空间预分配  //#define SDS_MAX_PREALLOC (1024*1024)  //预先分配内存的最大长度为 1MB if (newlen < SDS_MAX_PREALLOC) //新长度小于“最大预分配长度”,就直接将扩展的新长度乘2 newlen *= 2; else newlen += SDS_MAX_PREALLOC; //新长度大于“最大预分配长度”,就在加上一个“最大预分配长度” newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1); //获得新的扩展空间的地址 if (newsh == NULL) return NULL; newsh->free = newlen - len; //更新新空间的未使用的空间free return newsh->buf;}

4.2 SDS内存释放策略—惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作。

  • 当要缩短SDS保存的字符串时,程序并不立即使用内存充分配来回收缩短后多出来的字节,而是使用表头的free成员将这些字节记录起来,并等待将来使用。

源代码如下:

void sdsclear(sds s) { //重置sds的buf空间,懒惰释放 struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); sh->free += sh->len; //表头free成员+已使用空间的长度len = 新的free sh->len = 0; //已使用空间变为0 sh->buf[0] = '0'; //字符串置空}

4.3 Redis源码注释

在sds.h文件中,有两个static inline的函数,分别是sdslen和sdsavail函数,你可以把它认为是一个static的函数,加上了inline的属性。而inline关键字仅仅是建议编译器做内联展开处理,而不是强制。

在sds.c中,几乎所有的函数所传的参数都是sds类型,而非表头sdshdr的地址,但是使用了通过sds指针运算从而求得表头的地址的技巧,因为sds是指向sdshdr结构buf成员的。通过sds.h/sdslen函数,来分析:

这里的关键就是sds类型是指向sdshdr结构buf成员。

struct sdshdr结构共有三个变量,其中sds指向的buf成员是一个柔性数组,它仅仅起到占位符的作用,并不占用该结构体的大小,因此sizeof(sizeof(struct sdshdr))大小为8字节。

由于一个SDS类型的内存是通过动态内存分配的,所以它的内存在堆区,堆由下往上增长,因此sds指针减区sizeof(struct sdshdr)的大小就得到了表头的地址,然后就可以通过”->”访问表头的成员。如下图:

static inline size_t sdslen(const sds s) { //计算buf中字符串的长度 struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); //s指针地址减去结构体大小就是结构体的地址 return sh->len;}

通过这种技巧,将表头结构隐藏起来,只对外公开sds类型。

以下源码注释可以访问放在这里:sds.c和sds.h源码注释

  • sds.h文件注释
#ifndef __SDS_H#define __SDS_H#define SDS_MAX_PREALLOC (1024*1024) //预先分配内存的最大长度为1MB#include #include typedef char *sds; //sds兼容传统C风格字符串,所以起了个别名叫sds,并且可以存放sdshdr结构buf成员的地址struct sdshdr { unsigned int len; //buf中已占用空间的长度 unsigned int free; //buf中剩余可用空间的长度 char buf[]; //初始化sds分配的数据空间,而且是柔性数组(Flexible array member)};static inline size_t sdslen(const sds s) { //计算buf中字符串的长度 struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len;}static inline size_t sdsavail(const sds s) { //计算buf中的未使用空间的长度 struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->free;}sds sdsnewlen(const void *init, size_t initlen); //创建一个长度为initlen的字符串,并保存init字符串中的值sds sdsnew(const char *init); //创建一个默认长度的字符串sds sdsempty(void); //建立一个只有表头,字符串为空"0"的sdssize_t sdslen(const sds s); //计算buf中字符串的长度sds sdsdup(const sds s); //拷贝一份s的副本void sdsfree(sds s); //释放s字符串和表头size_t sdsavail(const sds s); //计算buf中的未使用空间的长度sds sdsgrowzero(sds s, size_t len); //将sds扩展制定长度并赋值为0sds sdscatlen(sds s, const void *t, size_t len); //将字符串t追加到s表头的buf末尾,追加len个字节sds sdscat(sds s, const char *t); //将t字符串拼接到s的末尾sds sdscatsds(sds s, const sds t); //将sds追加到s末尾sds sdscpylen(sds s, const char *t, size_t len); //将字符串t覆盖到s表头的buf中,拷贝len个字节sds sdscpy(sds s, const char *t); //将字符串覆盖到s表头的buf中sds sdscatvprintf(sds s, const char *fmt, va_list ap); //打印函数,被 sdscatprintf 所调用#ifdef __GNUC__sds sdscatprintf(sds s, const char *fmt, ...) //打印任意数量个字符串,并将这些字符串追加到给定 sds 的末尾 __attribute__((format(printf, 2, 3)));#elsesds sdscatprintf(sds s, const char *fmt, ...); //打印任意数量个字符串,并将这些字符串追加到给定 sds 的末尾#endifsds sdscatfmt(sds s, char const *fmt, ...); //格式化打印多个字符串,并将这些字符串追加到给定 sds 的末尾sds sdstrim(sds s, const char *cset); //去除sds中包含有 cset字符串出现字符 的字符void sdsrange(sds s, int start, int end); //根据start和end区间截取字符串void sdsupdatelen(sds s); //更新字符串s的长度void sdsclear(sds s); //将字符串重置保存空间,懒惰释放int sdscmp(const sds s1, const sds s2); //比较两个sds的大小,相等返回0sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); //使用长度为seplen的sep分隔符对长度为len的s进行分割,返回一个sds数组的地址,*count被设置为数组元素数量void sdsfreesplitres(sds *tokens, int count); //释放tokens中的count个sds元素void sdstolower(sds s); //将sds字符串所有字符转换为小写void sdstoupper(sds s); //将sds字符串所有字符转换为大写sds sdsfromlonglong(long long value); //根据long long value创建一个SDSsds sdscatrepr(sds s, const char *p, size_t len); //将长度为len的字符串p以带引号""的格式追加到s末尾sds *sdssplitargs(const char *line, int *argc); //参数拆分,主要用于 config.c 中对配置文件进行分析。sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); //将s中所有在 from 中的字符串,替换成 to 中的字符串sds sdsjoin(char **argv, int argc, char *sep); //以分隔符连接字符串子数组构成新的字符串/* Low level functions exposed to the user API */sds sdsMakeRoomFor(sds s, size_t addlen); //对 sds 中 buf 的长度进行扩展void sdsIncrLen(sds s, int incr); //根据incr的正负,移动字符串末尾的'0'标志sds sdsRemoveFreeSpace(sds s); //回收sds中的未使用空间size_t sdsAllocSize(sds s); //获得sds所有分配的空间#endif
  • sds.c文件注释
/* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-termined (all the sds strings are, always) so * even if you create an sds string with: * * mystring = sdsnewlen("abc

c语言追加字符串_Redis源码解析二--简单动态字符串相关推荐

  1. Redis源码-String:Redis String命令、Redis String存储原理、Redis String三种编码类型、Redis字符串SDS源码解析、Redis String应用场景

    Redis源码-String:Redis String命令.Redis String存储原理.Redis String三种编码类型.Redis字符串SDS源码解析.Redis String应用场景 R ...

  2. 【深度学习模型】智云视图中文车牌识别源码解析(二)

    [深度学习模型]智云视图中文车牌识别源码解析(二) 感受 HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等. 代码不可谓不混乱(别忘了这是职业公司的准产品级代码 ...

  3. Thrift源码解析(二)序列化协议

    概述 对于一个RPC框架,定义好网络数据的序列化协议是最基本的工作,thrift的序列化协议主要包含如下几种: TBinaryProtocol TCompactProtocol TJSONProtoc ...

  4. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

  5. Kubernetes学习笔记之Calico CNI Plugin源码解析(二)

    女主宣言 今天小编继续为大家分享Kubernetes Calico CNI Plugin学习笔记,希望能对大家有所帮助. PS:丰富的一线技术.多元化的表现形式,尽在"360云计算" ...

  6. php cut截取字符串,php源码分析之DZX1.5字符串截断函数cutstr用法

    本文实例讲述了php源码分析之DZX1.5字符串截断函数cutstr用法.分享给大家供大家参考.具体分析如下: /** * 函数来源DZX1.5,文件所在 /source/function/funct ...

  7. Mobx 源码解析 二(autorun)

    前言 我们在Mobx 源码解析 一(observable)已经知道了observable 做的事情了, 但是我们的还是没有讲解明白在我们的Demo中,我们在Button 的Click 事件中只是对ba ...

  8. android网络框架retrofit源码解析二

    注:源码解析文章参考了该博客:http://www.2cto.com/kf/201405/305248.html 前一篇文章讲解了retrofit的annotation,既然定义了,那么就应该有解析的 ...

  9. jtoken判断是否包含键_Redis源码解析十三--有序集合类型键实现(t_zset)

    有序集合类型键实现 1. 有序集合命令 Redis有序集合命令如下表所示:Redis 有序集合命令详解 2. 有序集合类型实现 有序集合对象的底层实现类型如下表: 关于底层的数据结构剖析和实现,请看如 ...

最新文章

  1. Unity3D学习笔记(二) 一些常用的空间函数
  2. 使用密钥登录CentOS系统(基于密钥的认证)
  3. 3-3numpy:向量与矩阵的计算,矩阵的逆
  4. javascript中的变量如果没有定义就使用的话
  5. 万圣节主题海报设计,少不了的素材
  6. jQuery获取不到隐藏DIV的高度和宽度
  7. oem是什么生产方式
  8. 分享 野人老师-高级信息系统项目管理师-全部的课程资料
  9. pyserial安装失败
  10. GPS经纬度转百度地图经纬度
  11. 无人机无线电干扰原理概论
  12. 英语语法之四大基本句式
  13. cocos2dx 游戏中内存优化
  14. curl 增加header_Curl发送header头信息
  15. 本人亲测,可以使用,万网虚拟主机绑定多个子域名方法(转载)
  16. AI行业态势感知(第七期)
  17. 【Redis】Redis高可用之Sentinel哨兵模式详解(Redis专栏启动)
  18. 网络--keytool自签名SSL证书(免费)以及私钥签名、公钥验签
  19. jExcel 创建基于 Web 的电子表格应用
  20. Nutz自定义SQL

热门文章

  1. LoRa VS NB-IoT,一场物联网时代 C 位争夺战
  2. FPGA 无解漏洞 “StarBleed”轰动一时,今天来扒一下技术细节!
  3. 产业区块链:新基建中的底层技术基座,各行业资深人士应积极参与建设
  4. 亚马逊机器学习工程师面试怎么过?
  5. 如何用 ASP.NET Core 实现熔断和降级?
  6. Google 向 Android 开发者支付了 800 亿美元,却仍不及苹果!
  7. 如何简单粗暴地上手 TensorFlow 2.0?
  8. FaceApp 一键变老?别丧失了你的隐私!
  9. “拳打”苹果“脚踢”三星,荣耀 20 DXO 全球第二,仅次华为 P30!
  10. 调查 10,500 名 Java 开发者发现,收费的 OracleJDK 仍是主流、IntelliJ IDEA 最受欢迎...