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

3 篇文章 0 订阅

      最近打算花较多的时间学习一下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))
      为什么要分成单字符和多字符的比较。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值