Redis设计与实现读书笔记(一) SDS

  作为redis最基础的底层数据结构之一,SDS提供了许多C风格字符串所不具备的功能,为之后redis内存管理提供了许多方便。它们分别是:

  • 二进制安全
  • 减少字符串长度获取时间复杂度
  • 杜绝字符串溢出
  • 减少内存分配次数
  • 兼容部分C语言函数  

 下面将简要阐述SDS基础结构,并介绍这些功能相应的实现细节。

 SDS字符类型定义非常简单,以redis3.0.7为例:

typedef char *sds;

struct sdshdr {
    unsigned int len;    //定义当前字符串长度(不包含‘\0‘)
    unsigned int free;   //定义当前对象可容纳的空间数
    char buf[];
};

  redis字符串定义的数据结构相当简单。作者从五个方面阐述了这样设计的好处:

  1.二进制安全

  C风格的字符串中的字符必须符合某种编码(ASCII),而且除了末尾以外不能保存空字符(‘\0’),而许多诸如图像,音频这样的二进制文件很有可能含有空字符。这样C风格字符串在处理某些二进制数据时可能发生数据截断,不是二进制安全。而redis作为数据库,必然要兼顾各种数据的存储,因此,通过sdshdr的len字段可以记录真实数据大小,保证二进制安全,换言之,写的数据是怎么样的,读取的时候也是怎么样的。

  2.减少字符串长度获取时间复杂度

  这个非常好理解,C风格的字符串通过遍历字符串找到‘\0’来得到字符串长度的,这样在字符串大量进行strlen操作时,会耗费许多时间。而sds在获取字符串长度时只需要读取len值即可,并且在每次对字符串进行修改时,len的变化对用户是透明的,使用一定的空间来换取速度,我认为是很合理的,许多高级语言也是这么来实现的,比如Java。

  3.杜绝字符串溢出

  使用C语言编程时,使用诸如strcat(str1,str2)字符串连接的函数,C语言假设程序员已经为str1分配了充足的空间以进行字符串的拼接。这时如果str1没有足够的空间的话,势必会造成内存泄漏。sds保证每次拼接时的操作总是正确无误的,假设空间不足,sds将启动自己的内存分配策略进行“扩容”,保证操作的正确性,避免了内存泄漏。

/* Append the specified null termianted C string to the sds string ‘s‘.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

  sds通过sdscat实现拼接的,这里通过sdscatlen实现方法,我们来看看sdscatlen。

/* Append the specified binary-safe string pointed by ‘t‘ of ‘len‘ bytes to the
 * end of the specified sds string ‘s‘.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {
    struct sdshdr *sh;
    size_t curlen = sdslen(s);    //当前长度

    s = sdsMakeRoomFor(s,len);    //进行空间分配,如果空间足够是不需要进行分配的
    if (s == NULL) return NULL;   //分配失败
    sh = (void*) (s-(sizeof(struct sdshdr))); //s的结构体指针首部
    memcpy(s+curlen, t, len);                 //直接进行了字节拷贝,因为已经将需要的内存分配好了
    sh->len = curlen+len;                     //计算使用长度,长度就是当前字符串长度加上函数参数len
    sh->free = sh->free-len;                  //分配完空间后会有一个free,这时用掉了len个空间所以要减去
    s[curlen+len] = ‘\0‘;                     //s的末尾添加‘\0’
    return s;
}

  函数的功能是将void指针指向的len个字节拼接到s上面。它首先计算了一下所需的空间和当前空间,并进行了一次空间分配,这次分配是通过sdsMakeRoomFor来实现的,我们待会会提到,分配好了以后,由于空间上一定是足够的,所以直接肆无忌惮的调用系统函数进行了拷贝,之后修改相应的属性值完成了修改。通过自定制的内存机制避免了程序员编程时遇到的内存泄漏。

  4、减少内存分配次数

  现在来看一下内存分配的sdsMakeRoomFor源码。

/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 *
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    if (free >= addlen) return s;    //空间充足
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));  //新的使用空间
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;                          //分配两倍空间(1份多余空间)
    else
        newlen += SDS_MAX_PREALLOC;
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);  //分配空间
    if (newsh == NULL) return NULL;

    newsh->free = newlen - len;                           //只修改free属性,len需要自行修改
    return newsh->buf;
}

  函数的功能是现在有一个sds指针,需要添加addlen时的分配策略。注意这里的sds不会去改变新的len,所以需要在调用这个函数后自行修改len属性。

  从这里面可以看出sds在内存管理的策略:

  (一)空间足够直接返回了,不做任何操作。

  (二)空间不足时,如果当前空间+需要空间 < SDS_MAX_PREALLOC(1M) 那么,就分配 2*(当前空间+需要空间),如果大于1M,分配(当前空间+需要空间 + 1M),这样的话,既不会耗费过多内存,也可以减少内存分配策略,设计上很简单精妙。

  5.兼容C部分函数

  其实像java一样,直接把所有字符放在数组里,再加上len属性是不需要结束符‘\0’的,但是sds为了兼容部分C函数,还是给字符串末尾加上了空字符,毕竟Java有自己原生的System.out而C语言只有自己的printf/scanf函数用于输入输出。

  另外,为了兼容c语言程序,sds的指针其实指向的是结构体里面的char[]的,所以用到len等属性需要进行转化。

  总结:

  redis自己设计了一套迷你的字符串系统,让初学者的我茅塞顿开,这是我的第一篇博客,希望以后可以坚持写下去。

 

时间: 2024-08-06 08:30:05

Redis设计与实现读书笔记(一) SDS的相关文章

Redis 设计与实现读书笔记一 Redis字符串

1 Redis 是C语言实现的 2 C字符串是 /0 结束的字符数组 3 Redis具体的动态字符串实现 /* * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中剩余可用空间的长度 int free; // 数据空间 char buf[]; }; sdshdr free 0 len 5 buf ---> 'R' 'e' 'd' 'i' 's' '/0' 4 感觉更像 Java 中的 StringBuffer 的设计

Linux内核设计与实现 读书笔记 转

Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://blog.csdn.net/yrj/article/category/718110 Linux内存管理和性能学习笔记(一) :内存测量与堆内存 第一篇 内存的测量 2.1. 系统当前可用内存 # cat /proc/meminfoMemTotal:        8063544 kBMemFree:       

Linux内核设计与实现读书笔记——第三章

Linux内核设计与实现读书笔记——第三章 进程管理 20135111李光豫 3.1进程 1.进程即处于执行期的程序,并不局限于一个可执行的代码,是处于执行期程序以及其相关资源的总称. 2.Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程. 3.Linux系统中,常用fork()进程创建子进程.调用fork()进程的成之为其子进程的父进程. 4.fork()继承实际上由clone()系统调用实现.最后通过exit()退出执行. 3.2任务描述符及任务结构 1.任务队列实质上

《Redis in action》读书笔记

https://www.gitbook.io/book/abcfy2/redis-in-action-reading-notes 最近在学习redis,正在看这本书,以笔记的形式记录下这本书,方便以后翻阅. 这本书介绍很不错,入门很值得参考,图文并茂解说redis的存储数据类型,很容易理解. 其中范例代码使用python编写,对熟悉python的用户很容易上手. 每章记录一下几个要点. 源码托管在github: https://github.com/abcfy2/redis-in-action-

《点石成金-访客至上的web和移动可用性设计秘籍》读书笔记

简介 作者Steve Krug,惯例先去了解一下本书的作者,发现书中介绍的并不多,百度一下后发现这本书比作者出名.好吧,百度就是这样子,作者自称web可用性咨询师,手上这本书是第三版再版,第一版2000年出版,2013年出版第三版,最大的变化大概是综合啦,原来凉拌是网站的可用性,这次是web和mobile.作者书中说,互联网时代,13年就像100年那么久,想想我们的cpu和手机这3年的更替,何况现在都16年啦.我想这本书之所以称为互联网必读书之一大概是因为它定义啦许多“本该如此”的原则问题,就是

《css设计指南》 读书笔记 一

<css设计指南>这本书是一个大神同学介绍给我的,据说覆盖了几乎所有前端面试的有关css的知识点,所以赶紧买来看看.( ps:这本书貌似绝版了,可以上淘宝买复印本,也可直接看电子书. ) 闭合标签,自闭合标签. html5中,将忽略所有自闭合标签最后的 / .可是建议在最后仍然加上  空格加/ 以规范格式. 所有img标签都需要加上alt属性. ps:视障用户使用的屏幕阅读器会读出图片的alt属性. 行内.块级标签.  块级: h1~h6, p, ol/ul, li, blockquote .

《设计心理学》读书笔记

<设计心理学>,英文原名为<The design of everyday things>,尽管书中确实提到了一些认知心理学和行为心理学中的概念,并且其作者--诺曼博士--也从事过心理学研究,但是对于书名是否应该被翻译为设计心理学,却依然是一个备受争议的话题. 回归书的内容本身,作者强调的是,日常物品的设计,应以易于使用为重.一款产品设计出来,最终是要落到用户手里的,其与用户的交互过程是否顺畅,直接决定了这款产品设计的成功与否.而针对目前设计界过分崇尚外观美学的现实,书中则多次用&q

C++——设计与演化——读书笔记

<<c++设计与演化>>1.c++的保护模式来自于访问权限许可和转让的概念; 初始化和赋值的区分来自于转让能力的思考; c++的const概念是从读写保护机制中演化出来. 2.BCPL注释: CPL(Combined Programming language,组合编程语言):CPL是一种在ALGOL60基础上更接近硬件的一种语言.CPL规模大,实现困难. BCPL(Basic Combined Programming language,基本的组合编程语言):BCPL是对CPL进行简

《恰如其分的软件架构:风险驱动的设计方法》——读书笔记

个人觉得本书概念太多,软件的架构和开发不是概念拼成的,一些经验性的东西用合适的词描述就行. 所谓风险驱动,其实就根据项目情况选择合适的设计力度,避免过度设计.而对于复杂的软件系统,精心设计还是非常重要的,在开发前和开发中都会有设计的问题. 另外,对于一个软件工程师,对一些架构模式需要有些了解,在脑海中构建所谓的“概念模型”,而每个模型都是一定的抽象和受到一些约束. 还介绍了领域模型,设计模型,代码模型,封装和分割,模型元素(组件),模型关系. 架构风格,就是所谓的概念模型. 第二部分值得一看.