作为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 *= ; //分配两倍空间(1份多余空间)
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+); //分配空间
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自己设计了一套迷你的字符串系统,让初学者的我茅塞顿开,这是我的第一篇博客,希望以后可以坚持写下去。

 

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

  1. Redis设计与实现读书笔记——简单动态字符串

    前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...

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

    1 Redis 是C语言实现的 2 C字符串是 /0 结束的字符数组 3 Redis具体的动态字符串实现 /* * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空 ...

  3. Redis设计与实现读书笔记(二) 链表

    链表作为最基础的数据结构,在许多高级语言上已经有了很好的实现.由于redis采用C语言编写,需要自己实现链表,于是redis在adlist.h定义了链表类型.作者对于这部分没什么好说,源码比较简单,如 ...

  4. <<redis设计和实现>>读书笔记

    redis如何实现主从同步的高效率?? 主从复制的同步有一个命令数据的同步文本,然后利用两个不同服务器的偏移量来进行进行同步,避免每次都是全部同步(并非会保存所有的命令数据,而是会有一个缓冲区(比如1 ...

  5. Redis设计与实现读书笔记——双链表

    前言 首先,贴一下参考链接: http://www.redisbook.com/en/latest/internal-datastruct/adlist.html, 另外真赞文章的作者,一个90后的小 ...

  6. Redis 设计与实现读书笔记一 Redis List

    list结构体 adlist.h/list(源码位置) /* * 双端链表结构 */ typedef struct list { // 表头节点 listNode *head; // 表尾节点 lis ...

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

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

  8. 【2018.08.13 C与C++基础】C++语言的设计与演化读书笔记

    先占坑 老实说看这本书的时候,有很多地方都很迷糊,但却说不清楚问题到底在哪里,只能和Effective C++联系起来,更深层次的东西就想不到了. 链接: https://blog.csdn.net/ ...

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

      简介 作者Steve Krug,惯例先去了解一下本书的作者,发现书中介绍的并不多,百度一下后发现这本书比作者出名.好吧,百度就是这样子,作者自称web可用性咨询师,手上这本书是第三版再版,第一版2 ...

随机推荐

  1. php N 维数组的读取、设置、删除

    <?php // 例子 $rowList = array(); $rowList[] = array('A'=>'A_1','B'=>'A_1_1','C'=>'A_1_1_1 ...

  2. mysql主从之slave-skip-errors和sql_slave_skip_counter

    一般来说,为了保险起见,在主从库维护中,有时候需要跳过某个无法执行的命令,需要在slave处于stop状态下,执行 set global sql_slave_skip_counter=1以跳过命令.但 ...

  3. Css3新特性总结之边框与背景(二)

    一.条纹背景 利用background为linear-gradient函数实现,linear-gradient取值如下: <angle>:角度,渐变的方向 to left right to ...

  4. xampp与Hbuilder、phpstorm配置

    1.xampp的安装就不用多说了,安装完按之后出现这个界面. 2.点击中间那个按钮,运行三个选项,全部正常之后是这样的,这样xampp就不用管了,但是要记下安装的路径,之后需要用 3.首先说Hbuil ...

  5. Andriod 自定义控件之音频条

    今天我们实现一个直接继承于View的全新控件.大家都知道音乐播放器吧,在点击一首歌进行播放时,通常会有一块区域用于显示音频条,我们今天就来学习下,播放器音频条的实现. 首先我们还是先定义一个类,直接继 ...

  6. IOS 杂笔-14(被人遗忘的owner)

    *owner在开发中现在已经很少用了 有兴趣的童鞋可以看看* 我们遇到owner通常是在类似 [[[NSBundle mainBundle] loadNibNamed:@"Food" ...

  7. AngularJS HTML DOM& 事件

    AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令. ng-disabled 指令直接绑定应用程序数据到 HTML 的 disabled 属性 <div ng-app= ...

  8. 原创 C++之常量(二)

    4常量的内存分配 4.1应用程序的内存结构 一个由C++编译的应用程序,占用的内存可以划分为如下几个部分: 栈(stack).由编译器自动分配释放.存放函数参数和函数里的局部变量(又称自动变量).其操 ...

  9. SQL删除语句同时向备份表插入数据

    从这里摘抄下来的,觉得很不错,http://www.cnblogs.com/ljhdo/p/5792886.html#3503524 ,以后就用这种方式删除,再也不用担心删除错数据啦!!!

  10. SQLite学习笔记(十一)&&虚拟机原理

    前言      我们知道任何一种关系型数据库管理系统都支持SQL(Structured Query Language),相对于文件管理系统,用户不用关心数据在数据库内部如何存取,也不需要知道底层的存储 ...