[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 本质上是对char类型的重构,在char的基础上加入了两个int空间存储字符串长度和剩余空间。
2.为什么要typedef char *sds;而不是 typedef struct sdshdr *sds;?
sds是指向char的指针,但它是通过先构造一个sdshdr的struct,然后指向其中的buf内存空间来实现的。
sds如此设计的好处在书中有详细的讲述。
3.sdscatvprintf 函数的学习:
可变参数的步骤如下:
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。
举个例子说明上述函数的功能和用法:
函数原型:int vsnprintf(char *str, size_t size, const char *format, va_list ap);
函数说明:将可变参数格式化输出到一个字符数组
参数:
str输出到的数组,size指定大小,防止越界,format格式化参数,ap可变参数列表
举例:
4.学习sdssplitlen函数,有个疑问
这篇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))为什么要分成单字符和多字符的比较。
相关文章推荐
- Redis源码学习1——基本数据结构sds
- Redis源码学习1——基本数据结构sds
- Redis源码学习4-基本数据结构之字典
- 【学习笔记】Redis(1)-数据结构
- redis源码学习-sds字符串结构
- redis五种数据结构原理学习解析
- Redis学习笔记~Redis提供的五种数据结构
- Redis源码分析(1)-底层数据结构SDS
- redis系列笔记之数据结构SDS
- redis底层数据结构之sds
- 学习Redis你必须了解的数据结构——JS实现集合和ECMA6集合
- Redis内部数据结构详解之简单动态字符串(sds)
- Redis源码学习4-基本数据结构之字典
- redis学习-sds数据类型
- Redis内部数据结构详解(2)--sds
- Redis学习——SDS字符串源码分析
- Redis源码学习3-基本数据结构之双向链表
- 十五分钟介绍 Redis数据结构--学习笔记
- Redis系统学习 三、使用数据结构
- Redis内部数据结构详解之简单动态字符串(SDS)