Redis源码学习1——基本数据结构sds

51 篇文章 0 订阅
27 篇文章 0 订阅

最近好几次用到redis,但是一直没有时间来好好学习下redis的原理,打算最近花一个月的空余时间来整理学习下redis的源码。redis本身非常简洁,下载源码直接make就ok了,而且似乎没有依赖第三方库什么的。先从基本的数据结构开始把,主要参考了http://www.redisbook.com/en/latest/index.html#, 非常赞的关于redis 的站点,作者huangz1990对redis的分析非常清楚易懂,作者提供的有详细注释的源代码地址:https://github.com/huangz1990/annotated_redis_source。在此基础上我读读源码。好久不搞c语言了,看起来真陌生。先从基本数据结构看起吧,首先分析sds.c文件。

1.sdshdr结构

[cpp]  view plain copy
  1. // sds 类型  
  2. typedef char *sds;  
  3.   
  4. // sdshdr 结构  
  5. struct sdshdr {  
  6.   
  7.     // buf 已占用长度  
  8.     int len;  
  9.   
  10.     // buf 剩余可用长度  
  11.     int free;  
  12.   
  13.     // 实际保存字符串数据的地方  
  14.     char buf[];  
  15. };  

看来sdshdr结构还是挺简单的,主要用来管理字符串,之所以另外设计出一个结构,作者自有用意了,管理字符串比原生的字符串方便高效许多。

这样我们如果构建一个字符串“hello world”, 那么在sdshdr结构体中表示起来就是这个样子的了:
[cpp]  view plain copy
  1. struct sdshdr {  
  2.     len = 11;  
  3.     free = 0;  
  4.     buf = "hello world\0";  // buf 的实际长度为 len + 1  
  5. };  

2.sdsnew构建字符串

构建字符串的函数sdsnew如下:
[cpp]  view plain copy
  1. /* 
  2.  * 根据给定初始化值 init ,创建 sds 
  3.  * 如果 init 为 NULL ,那么创建一个 buf 内只包含 \0 终结符的 sds 
  4.  * 
  5.  * T = O(N) 
  6.  */  
  7. sds sdsnew(const char *init) {  
  8.     size_t initlen = (init == NULL) ? 0 : strlen(init);  
  9.     return sdsnewlen(init, initlen);  
  10. }  

可以发现,sdsnew调用sdsnewlen来构建字符串。sdsnewlen代码如下:

[cpp]  view plain copy
  1. /* 
  2.  * 创建一个指定长度的 sds  
  3.  * 如果给定了初始化值 init 的话,那么将 init 复制到 sds 的 buf 当中 
  4.  * 
  5.  * T = O(N) 
  6.  */  
  7. sds sdsnewlen(const void *init, size_t initlen) {  
  8.   
  9.     struct sdshdr *sh;  
  10.   
  11.     // 有 init ?  
  12.     // O(N)  
  13.     if (init) {  
  14.         sh = zmalloc(sizeof(struct sdshdr)+initlen+1);  
  15.     } else {  
  16.         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);  
  17.     }     
  18.   
  19.     // 内存不足,分配失败  
  20.     if (sh == NULL) return NULL;  
  21.   
  22.     sh->len = initlen;  
  23.     sh->free = 0;  
  24.   
  25.     // 如果给定了 init 且 initlen 不为 0 的话  
  26.     // 那么将 init 的内容复制至 sds buf  
  27.     // O(N)  
  28.     if (initlen && init)  
  29.         memcpy(sh->buf, init, initlen);  
  30.   
  31.     // 加上终结符  
  32.     sh->buf[initlen] = '\0';  
  33.   
  34.     // 返回 buf 而不是整个 sdshdr  
  35.     return (char*)sh->buf;  
  36. }  
这里主要是根据字符串长度来分配内存空间。如果指定了init,则调用zmalloc分配内存,否则调用zcalloc分配内存。需要注意的是,redis对内存分配和释放过程做了封装,具体在下一篇文章再单独分析。zmalloc和zcalloc的区别无非是zmalloc不会初始化分配的内存,而zcalloc则会将分配的内存初始化为0。分配内存大小都为initlen+1+sdshdr大小。需要注意的是,返回值为sdshdr结构体的buf。buf是一个柔性数组,在结构体中并没有设定元素数目。所以,结构体sdshdr本身的大小其实是2个int的大小。

3.sdscatsds 字符串扩展

使用sdshdr结构体,在append字符串的时候就非常方便了。如果我们在“hello world”后append“ again!“,则sdshdr会更新为下面这个样子:
[cpp]  view plain copy
  1. struct sdshdr {  
  2.     len = 18;  
  3.     free = 18;  
  4.     buf = "hello world again!\0                  ";     // 空白的地方为预分配空间,共 18 + 18 + 1 个字节  
  5. }  
append操作在函数sdscat中实现,而sdscat又是调用sdscatlen中实现。
[cpp]  view plain copy
  1. /* 
  2.  * 将一个 char 数组拼接到 sds 末尾  
  3.  * 
  4.  * T = O(N) 
  5.  */  
  6. sds sdscat(sds s, const char *t) {  
  7.     return sdscatlen(s, t, strlen(t));  
  8. }  
sdscatlen首先获取当前sds的长度,然后扩展大小。并将字符串拼接到原来的sds末尾。更新len和free大小,并设定终结符\0。
[cpp]  view plain copy
  1. /* 
  2.  * 按长度 len 扩展 sds ,并将 t 拼接到 sds 的末尾 
  3.  * 
  4.  * T = O(N) 
  5.  */  
  6. sds sdscatlen(sds s, const void *t, size_t len) {  
  7.   
  8.     struct sdshdr *sh;  
  9.   
  10.     size_t curlen = sdslen(s);  
  11.   
  12.     // O(N)  
  13.     s = sdsMakeRoomFor(s,len);  
  14.     if (s == NULL) return NULL;  
  15.   
  16.     // 复制  
  17.     // O(N)  
  18.     memcpy(s+curlen, t, len);  
  19.   
  20.     // 更新 len 和 free 属性  
  21.     // O(1)  
  22.     sh = (void*) (s-(sizeof(struct sdshdr)));  
  23.     sh->len = curlen+len;  
  24.     sh->free = sh->free-len;  
  25.   
  26.     // 终结符  
  27.     // O(1)  
  28.     s[curlen+len] = '\0';  
  29.   
  30.     return s;  
  31. }  

append操作的时候内存分配策略由sdsMakeRoomFor函数负责,代码如下:
[cpp]  view plain copy
  1. sds sdsMakeRoomFor(  
  2.     sds s,  
  3.     size_t addlen   // 需要增加的空间长度  
  4. )  
  5. {  
  6.     struct sdshdr *sh, *newsh;  
  7.     size_t free = sdsavail(s); //根据sds找出sdshdr结构体指针位置,并得到结构体free字段的值  
  8.     size_t len, newlen;  
  9.   
  10.     // 剩余空间可以满足需求,无须扩展  
  11.     if (free >= addlen) return s;  
  12.   
  13.     sh = (void*) (s-(sizeof(struct sdshdr)));  
  14.   
  15.     // 目前 buf 长度  
  16.     len = sdslen(s);  
  17.     // 新 buf 长度  
  18.     newlen = (len+addlen);  
  19.     // 如果新 buf 长度小于 SDS_MAX_PREALLOC 长度  
  20.     // 那么将 buf 的长度设为新 buf 长度的两倍  
  21.     if (newlen < SDS_MAX_PREALLOC)  
  22.         newlen *= 2;  
  23.     else  
  24.         newlen += SDS_MAX_PREALLOC;  
  25.   
  26.     // 扩展长度  
  27.     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);  
  28.   
  29.     if (newsh == NULL) return NULL;  
  30.   
  31.     newsh->free = newlen - len;  
  32.   
  33.     return newsh->buf;  
  34. }  
如果free大于要增加的长度,则直接返回。否则,则需要扩展。在2.6版本中,SDS_MAX_PREALLOC大小为1M,即如果newlen大于1M,则增加1M。否则则newlen更新为原来的两倍。并更新free的值,返回buf。

4.sdsclear 清除

清除sds的函数为sdsclear,代码如下:
[cpp]  view plain copy
  1. void sdsclear(sds s) {  
  2.   
  3.     struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //获取sdshdr结构体地址  
  4.   
  5.     sh->free += sh->len; //free加上字符串长度  
  6.     sh->len = 0;  //len设为0  
  7.     sh->buf[0] = '\0'//buf第0个元素设为\0  
  8. }  
针对前面的“hello world”构建的sds调用sdsclear,则此时sdshdr结构体看起来就如下面一样了:

[cpp]  view plain copy
  1. struct sdshdr {  
  2.     len = 0;  
  3.     free = 11;  
  4.     buf = "\0ello world\0";    
  5. };  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值