sds是redis中用来处理字符串的数据结构。sds的定义在sds.h中:

  1. typedef char *sds;

简洁明了!简明扼要!(X,玩我呢是吧!这特么不就是c中的字符串么?!)。像redis这种高端大气上档次的服务器显然不会这么的幼稚。在sds的定义之后,还有一个结构体:

  1. struct sdshdr {
  2. int len;
  3. int free;
  4. char buf[];
  5. }

有len,有free,这就有点意思了。很明显,根据这个结构体的定义,这是sds的header,用来存储sds的信息。注意最后的buf定义,这个buf数组没有设置长度。这是为神马呢?在gcc中,这种方式可以使得buf成为一个可变的数组,也就是说,可以扩展buf同时又保证在使用的时候,感觉buf始终在struct sdshdr中。有点啰嗦,其实可以用下图展示:

  1. sdshdr sds
  2. | |
  3. V V
  4. ----------------------------
  5. |len | free | buf |
  6. ----------------------------

这个就是sds的内存分布图。struct sdshdr这个结构体放在了真正的数据之前,且是紧挨着的。这样,通过buf引用的数组其实就是后面的数据。这个是利用了c中数组访问的特点。
下面我们来看看如何创建一个sds:

  1. /* Create a new sds string with the content specified by the 'init' pointer
  2. * and 'initlen'.
  3. * If NULL is used for 'init' the string is initialized with zero bytes.
  4. *
  5. * The string is always null-termined (all the sds strings are, always) so
  6. * even if you create an sds string with:
  7. *
  8. * mystring = sdsnewlen("abc",3");
  9. *
  10. * You can print the string with printf() as there is an implicit \0 at the
  11. * end of the string. However the string is binary safe and can contain
  12. * \0 characters in the middle, as the length is stored in the sds header. */
  13. sds sdsnewlen(const void *init, size_t initlen) {
  14. struct sdshdr *sh;
  15.  
  16. if (init) {
  17. sh = zmalloc(sizeof(struct sdshdr)+initlen+);
  18. } else {
  19. sh = zcalloc(sizeof(struct sdshdr)+initlen+);
  20. }
  21. if (sh == NULL) return NULL;
  22. sh->len = initlen;
  23. sh->free = ;
  24. if (initlen && init)
  25. memcpy(sh->buf, init, initlen);
  26. sh->buf[initlen] = '\0';
  27. return (char*)sh->buf;
  28. }

重点是这句(zcalloc也一样,只是分配内存的时候顺带初始化为0):

  1. sh = zmalloc(sizeof(struct sdshdr)+initlen+)

创建一个sds的时候,实际申请的内存大小为sdshdr的大小,加上调用者希望的sds的大小,再加一。另外,zmalloc的返回值直接赋值给了sh,sh是struct sdshdr。那么,在创建一个sds的时候,将sds的struct sdshdr放到了真正的数据的前面,这样可以通过buf引用到后面的数据。多加一个一是为了保证有地方放'\0'。根据注释,sds默认以'\0'结尾,且可以存放二进制的数据,因为struct sdshdr中存放了数据的长度。在sdsnewlen的最后,返回的是(char\*)sh->buf,也就是说sds实际指向的就是一个char\*数组。**所有可以对char\*的操作也同时可以操作sds**。

那sds的长度等信息如何获取呢?看下面的代码:

  1. static inline size_t sdslen(const sds s) {
  2. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
  3. return sh->len;
  4. }
  5.  
  6. static inline size_t sdsavail(const sds s) {
  7. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
  8. return sh->free;
  9. }

这两个函数分别是获取sds的实际长度和可用空间。核心代码就是这句:

  1. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

将sds的地址减去struct sdshdr的长度然后赋值给sh,这就得到了sds对应的struct sdshdr。根据前面的内存分布图,struct sdshdr始终是在数据的前面,一次很容易得到struct sdshdr的地址。得到了struct sdshdr的地址之后,其他的就很简单了。

sds支持动态的扩展空间,sdsMakeRoomFor这个函数用来扩展sds的空间:

  1. /* Enlarge the free space at the end of the sds string so that the caller
  2. * is sure that after calling this function can overwrite up to addlen
  3. * bytes after the end of the string, plus one more byte for nul term.
  4. *
  5. * Note: this does not change the *length* of the sds string as returned
  6. * by sdslen(), but only the free buffer space we have. */
  7. sds sdsMakeRoomFor(sds s, size_t addlen) {
  8. struct sdshdr *sh, *newsh;
  9. size_t free = sdsavail(s);
  10. size_t len, newlen;
  11.  
  12. if (free >= addlen) return s;
  13. len = sdslen(s);
  14. sh = (void*) (s-(sizeof(struct sdshdr)));
  15. newlen = (len+addlen);
  16. if (newlen < SDS_MAX_PREALLOC)
  17. newlen *= ;
  18. else
  19. newlen += SDS_MAX_PREALLOC;
  20. newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+);
  21. if (newsh == NULL) return NULL;
  22.  
  23. newsh->free = newlen - len;
  24. return newsh->buf;
  25. }

这个函数保证sds至少有addlen长度的空间可用。这个函数体现了sds的空间扩展策略。如果有足够的空间,则直接返回。如果空间不够,当len+addlen小于SDS_MAX_PREALLOC时,将空间扩展到(len+addlen)\*2。当len+addlen大于SDS_MAX_PREALLOC,将空间扩展到len+addlen+SDS_MAX_PREALLOC。sds的扩展考虑了实际需要的空间大小,扩展的效率要高一些。如果每次扩大原来的二倍,当需要的空间大于初始空间二倍时,需要多次的扩展操作,也就意味着多次的zrealloc操作。sds的扩展可以在任何情况下一次扩展到位。

sds最大的特点就是所有可以对char\*的操作都可以操作sds,这在实际使用sds的的时候可以带来很多方便。比如,从socket中读取数据存储到sds中,可以如下操作:

  1. /* sds s */
  2. int oldlen = sdslen(s);
  3. s = sdsMakeRoomFor(s, BUFFER_SIZE);
  4. nread = read(fd, s+oldlen, BUFFER_SIZE);
  5. sdsIncrLen(s, nread);

在调用read的时候,可以把sds看做是char\*来处理(实际上sds就是char\*)。当然,最后一定要调用sdsIncrLen来修正sds的长度。

redis源码分析(3)sds的更多相关文章

  1. redis源码分析(一)-sds实现

    redis支持多种数据类型,sds(simple dynamic string)是最基本的一种,redis中的字符串类型大多使用sds保存,它支持动态的扩展与压缩,并提供许多工具函数.这篇文章将分析s ...

  2. Redis源码分析(sds)

    源码版本:redis-4.0.1 源码位置:https://github.com/antirez/sds 一.SDS简介 sds (Simple Dynamic String),Simple的意思是简 ...

  3. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  4. redis源码分析之事务Transaction(下)

    接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...

  5. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  6. Redis源码分析(intset)

    源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...

  7. Redis源码分析(dict)

    源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...

  8. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  9. [Redis源码阅读]sds字符串实现

    初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...

  10. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

随机推荐

  1. VS2017自动添加头部注释

    让VS自动生成类的头部注释,只需修改两个文集即可,一下两个路径下个有一个 Class.cs文件 D:\Program Files (x86)\Microsoft Visual Studio\2017\ ...

  2. [转]C#开发微信公众平台-就这么简单

    本文转自:http://www.it165.net/pro/html/201403/11102.html 写在前面 服务号和订阅号 URL配置 创建菜单 查询.删除菜单 接受消息 发送消息(图文.菜单 ...

  3. leetcode773

    使用两种语言实现,先贴C++的 class Solution { public: vector<vector<int>> floodFill(vector<vector& ...

  4. Delphi IOS 蓝牙锁屏后台运行

    Delphi IOS 后台运行 同样的程序,编译成android,锁屏后继续运行正常,蓝牙通讯正常,但在IOS下锁屏后程序的蓝牙就中断通讯了? IOS的机制就是这样,锁屏就关闭了. 音乐播放器是怎么做 ...

  5. ClientDataSet1.LoadFromStream

    lStream3.Position := 0;        lDataSet.LoadFromStream(lStream3);

  6. android获取mp4视频文件总时长和视频宽高<转>

    android使用 MediaMetadataRetriever 获取视频文件的 总时长 和视频的分辨率. 根据该方式获取视频信息可以看出不仅仅可以获取时长和分辨率,还能获取到其他的一些视频信息,不错 ...

  7. wince驱动开发入门

    因为课题前期调研没做好,用的CPU板卡和数据采集卡来自两个部门.加上买的是裸板,自己定制的OS,技术支持不爱搭理.所以给的AI板卡的驱动一直装不上,自己在郁闷中寻找答案,就扎进了wince驱动的知识库 ...

  8. pl/sql简介

  9. 获取文件的后缀名。phpinfo

    1: function get_extension($file){ //strrchr 返回 .jpg substr :1 是从1开始. substr(strrchr($file,'.'),1) } ...

  10. IWebBrowser和IE浏览器的行为不一样

    原本一直以为IWebBrowser2的行为和IE浏览器的行为应该是一样的,但是最近发现事实不是如此. IE8以后的浏览器都带有兼容模式,而IWebBrowser2默认情况下是在兼容模式下运行的,可以参 ...