Redis学习——SDS字符串源码分析

0. 前言

  这里对Redis底层字符串的实现分析,但是看完其实现还没有完整的一个概念,即不太清楚作者为什么要这样子设计,只能窥知一点,需要看完redis如何使用再回头来体会,有不足之处还望告知。

  涉及文件:sds.h/sds.c

1.  数据结构:  

1 typedef char *sds;
2 
3 struct sdshdr {
4     unsigned int len;    //buf中已使用的字节数
5     unsigned int free;    //buf中未使用的字节数
6     char buf[];        //缓冲区
7 };

  这里向外提供的api所返回的类型都是sds类型(字符串),这样的话也能够复用一部分的C字符串函数。

  这里采用sdshdr结构,存放了字符串长度信息,保证了二进制数据安全,即不仅可以存放字符串,也可用于存放其它二进制数据

2. API实现:

  只提取几个API,该文件完整的注释在GitHud上(用户名:jabnih)

a. sdsnewlen

  创建一个sds字符串,其它几个创建API都是基于这个API。

  创建时采用一次性分配其所需要的空间,即对于buf不进行再次分配,减少了malloc等的调用,同时在释放的时候也减少free次数

 1 //创建一个sds字符串,初始内容为init所指向的内容,buf空间为initlen大小
 2 sds sdsnewlen(const void *init, size_t initlen) {
 3     struct sdshdr *sh;
 4 
 5     //这里需要注意
 6     if (init) {
 7         //init不为空,则使用malloc,所申请的空间不会初始化
 8         sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
 9     } else {
10         //init为空,使用calloc,所申请的空间会被初始化为0
11         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
12     }
13 
14     if (sh == NULL) return NULL;
15 
16     sh->len = initlen;
17     sh->free = 0;
18     //这里如果init为NULL,则该buf的内容均为0
19     if (initlen && init)
20         memcpy(sh->buf, init, initlen);
21 
22     sh->buf[initlen] = '';
23 
24     return (char*)sh->buf;
25 }

b. sdsMakeRoomFor

  该API的内存分配策略为:在小于SDS_MAX_PREALLOC(即1M)时,会预分配出多一倍的空间,在大于该阈值时,每次只预分配多SDS_MAX_PREALLOC内存。

 1  //保证sds字符串有足够的剩余未使用空间(大于或等于addlen)
 2 sds sdsMakeRoomFor(sds s, size_t addlen) {
 3     struct sdshdr *sh, *newsh;
 4     size_t free = sdsavail(s);
 5     size_t len, newlen;
 6 
 7     //其剩余的空间满足addlen大小
 8     if (free >= addlen) return s;
 9 
10     //不满足addlen大小,需要重新分配
11     len = sdslen(s);
12     sh = (void*) (s-(sizeof(struct sdshdr)));
13     //新空间所需使用的大小为当前sds使用的长度加上addlen
14     newlen = (len+addlen);
15     //如果新空间大小比设定的阈值小,则以2倍的增长速度预分配一些空间
16     if (newlen < SDS_MAX_PREALLOC)
17         newlen *= 2;
18     else
19         //比设定阈值大,则只增加PREALLOC预分配大小
20         newlen += SDS_MAX_PREALLOC;
21     //重新分配空间
22     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
23     if (newsh == NULL) return NULL;
24 
25     newsh->free = newlen - len;
26     return newsh->buf;
27 }

c. sdsRemoveFreeSpace

 1  //去除sds字符串中未使用的空间,一般在内存紧张的时候使用
 2 sds sdsRemoveFreeSpace(sds s) {
 3     struct sdshdr *sh;
 4 
 5     sh = (void*) (s-(sizeof(struct sdshdr)));
 6     sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
 7     sh->free = 0;
 8 
 9     return sh->buf;
10 }

d. sdsclear

1  //清空sds字符串,但是不释放空间
2 void sdsclear(sds s) {
3     struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
4 
5     sh->free += sh->len;
6     sh->len = 0;
7     sh->buf[0] = '';
8 }

3. 总结:

  1. 二进制数据安全

  2. 预分配空间,可以懒惰释放,在内存紧张的时候也可以缩减不需要的内存

  3. 使用该API可以实现内存动态扩展(即不需要考虑内存空间是否足够)

  4. 边界检查

原文地址:https://www.cnblogs.com/jabnih/p/4733260.html