设为首页 收藏本站
查看: 1045|回复: 0

[经验分享] redis源码分析(3)sds

[复制链接]

尚未签到

发表于 2015-7-21 00:36:39 | 显示全部楼层 |阅读模式
  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中。有点啰嗦,其实可以用下图展示:



  sdshdr       sds
|           |
V           V
----------------------------
|len | free | buf …        |
----------------------------     
  这个就是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+1);
18     } else {
19         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
20     }
21     if (sh == NULL) return NULL;
22     sh->len = initlen;
23     sh->free = 0;
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+1)
  创建一个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 *= 2;
18     else
19         newlen += SDS_MAX_PREALLOC;
20     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
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的长度。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88789-1-1.html 上篇帖子: redis ruby客户端学习(一) 下篇帖子: Redis快速入门
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表