您的位置:首页 > 理论基础 > 数据结构算法

[Redis][数据结构]sds的学习

2017-03-20 21:41 351 查看
      最近打算花较多的时间学习一下Redis的源代码,参考的主要是huangz的《redis设计与实现》和一些博客,争取坚持下去,完成这个艰巨的任务。

      这篇blog记录了读完sds.h和sds.c两个文件后的一些疑惑和总结。

     1.对 struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));的理解。

      首先看下 S初始化的代码:

sds sdsnewlen(const void *init, size_t initlen) {

struct sdshdr *sh;

// 根据是否有初始化内容,选择适当的内存分配方式
// T = O(N)
if (init) {
// zmalloc 不初始化所分配的内存
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
// zcalloc 将分配的内存全部初始化为 0
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}

// 内存分配失败,返回
if (sh == NULL) return NULL;

// 设置初始化长度
sh->len = initlen;
// 新 sds 不预留任何空间
sh->free = 0;
// 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
// T = O(N)
if (initlen && init)
memcpy(sh->buf, init, initlen);
// 以 \0 结尾
sh->buf[initlen] = '\0';

// 返回 buf 部分,而不是整个 sdshdr
return (char*)sh->buf;
}
      S实际上是指向的buf首地址,然后s-(sizeof(struct sdshdr),即地址向前挪8个字节,指向的是 该sds的struct sdshdr的首地址。
      sds 本质上是对char类型的重构,在char的基础上加入了两个int空间存储字符串长度和剩余空间。

      2.为什么要typedef char *sds;而不是 typedef struct sdshdr *sds;?

      sds是指向char的指针,但它是通过先构造一个sdshdr的struct,然后指向其中的buf内存空间来实现的。

      sds如此设计的好处在书中有详细的讲述。

      3.sdscatvprintf 函数的学习:

/* 将格式化输出的参数连接到字符串s后面 */
//s:原字符串,fmt:指向要连接到s后面的字符串指针
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;

/* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
//如果fmt长度的2倍大于定义好的1024大小的staticbuf,再堆上分配一个新的缓冲区间,否则我实验定义好的srarixbuf
if (buflen > sizeof(staticbuf)) {
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}

/* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while(1) {
buf[buflen-2] = '\0';//将缓冲区的倒数第2位打上'\0'
va_copy(cpy,ap);
// T = O(N)
//格式化输出到缓冲区
vsnprintf(buf, buflen, fmt, cpy);//buf:输出到的数组,buflen:指定大小,fmt:格式,ap:可变参数列表
if (buf[buflen-2] != '\0') {//如果先前的标记被覆盖,则将缓冲区扩大为原来的2倍
if (buf != staticbuf) zfree(buf);
buflen *= 2;
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break;
}

/* Finally concat the obtained string to the SDS string and return it. */
//利用sdscat将buf连接到s后面
t = sdscat(s, buf);
if (buf != staticbuf) zfree(buf);
return t;
}
      这段代码涉及到了可变参数的概念,现简要地将学习笔记记录下来。
      可变参数的步骤如下:

      1.首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;

      2.然后用VA_START宏初始化变量刚定义的VA_LIST变量;

      3.然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);

      4.最后用VA_END宏结束可变参数的获取。

      各个函数的具体作用:

      va_list ap ;  定义一个va_list变量ap

      va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。

      va_arg(ap,t) , ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。 ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后 用*取得这个地址的内容。

      va_end(ap) ; 清空va_list ap。

      举个例子说明上述函数的功能和用法:

/*求输入数据的平均数*/
#include<stdarg.h>//可变参数宏定义所在的头文件
#include<stdio.h>
int AveInt(int v,...){
int ReturnValue=0;
int i=v;/v是要输入数据的个数
va_list ap;//定义va_list变量
va_start(ap,v);//初始化,ap指向的是v后面的那个参数的地址
while (i>0) {
ReturnValue+=va_arg(ap,int);//取出当前ap指针所指的值,并使ap指向下个参数
i--;
}
va_end(ap);//清空va_list
return ReturnValue/=v;
}

int main(){
printf("%d\t",AveInt(2,2,3));
printf("%d\t",AveInt(4,2,4,6,8));
return 0;
}
      源代码中又涉及到了vsnprintf函数的用法,我也总结如下:
      函数原型:int vsnprintf(char *str, size_t size, const char *format, va_list ap);

      函数说明:将可变参数格式化输出到一个字符数组

      参数:

      str输出到的数组,size指定大小,防止越界,format格式化参数,ap可变参数列表

      举例:

#include<stdio.h>
#include<stdarg.h>
void SYSTEM(const char *format,...){
char buff[4069];
va_list list;
va_start(list,format);//list指向format后面那个参数的地址
vsnprintf(buff,4069,format,list);
va_end(list);
printf("%s\n",buff);
}

int main(){
SYSTEM("%d %s",6,"abc");

return 0;
}

    4.学习sdssplitlen函数,有个疑问

if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0))
      为什么要分成单字符和多字符的比较。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: