redis学习笔记(1)---字符串sds

字符串

  在C语言中,字符串通常有以下两种方式来表示:

char *buf1="redis";
char buf2[]="redis";

  buf1是通过一个char指针指向一个字符串字面量,其内容是不能改变的,即不能使用buf1[3]=’c’;这种方式来改变字符串中的某个字符,要改变字符串内容只能通过给buf1指针重新赋值,因此不能重用buf1指向的内存空间。
  buf2是一个char数组,末尾会自动有一个字节的’\0’表示结束,其长度为6个字节。其内容虽然可以改变,但是由于buf2本身不携带长度等信息,因此调用一些字符串操作如strcat时,可能会导致缓冲区溢出,不安全。
  
  redis中没有使用C语言的这两种方式来表示字符串,而是通过一种名为简单动态字符串(simple dynamic string,SDS)的类型来表示的。
  C字符串只会作为字符串字面量在一些无需修改的地方用到,如打印日志:  

redisLogFromHandler(REDIS_WARNING, "You insist... exiting now.");

  在redis中,sds除了可以保存数据库的字符串键值对外,还可以用作AOF缓冲区、客户端状态中的缓冲区。

SDS

1、sds的定义

struct sdshdr {
    unsigned int len;//buf中已用长度
    unsigned int free;//buf中剩余可用长度
    char buf[];//保存字符串的数组
};
// sizeof(sdshdr) = 8

  示例如下:
  这里写图片描述
   buf总长度为11个字节,其中5个字节已用(len),5个字节未用(free),buf末尾总会有1个字节的’\0’表示字符串的结束,这一个字节不会计入SDS的长度属性中。
  

2、sds的创建、复制、清除与释放  

   创建:

sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;

    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);//+1用于存储'\0'
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    sh->len = initlen;//设置len和free的值
    sh->free = 0;
    if (initlen && init) //将init中的内容copy到sds中
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';//字符串结束符
    return (char*)sh->buf;
}

// 调用sdsnew来创建一个sds字符串,其中字符串的内容为init
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

  这样就完成了一个字符串的创建了,返回的是保存字符串的buf的起始地址。
  复制:

sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}

  清除:

void sdsclear(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    sh->free += sh->len;
    sh->len = 0;
    sh->buf[0] = '\0';
}

  释放:

void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}

3、sds的长度获取  

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

   由于sds在函数间传递时,使用的都是字符串的起始地址buf,因此sds结构体的起始地址为s-(sizeof(struct sdshdr))。
   找到sds结构体的起始地址后,就可以直接获取相关字段的值了。
   因此获取len和free的操作的时间复杂度都是O(1)。
  

4、sds空间扩展  

sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    if (free >= addlen) return s;//当剩余长度大于所需长度时,不需要扩展内存
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)//长度扩展方法
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);//重新分配内存
    if (newsh == NULL) return NULL;

    newsh->free = newlen - len;
    return newsh->buf;
}

   通过上面的代码可以发现,sds空间扩展不是直接将空间扩展为newlen = sds->len+addlen的,而是在此基础上,再将newlen扩大2倍。如sds->len = 10, addlen=20,则扩展后的新sds字符串的所有可用空间的长度为(10+20)*2=60字节。
  

SDS的优势:

   1. sds获取字符串长度的时间复杂度为O(1),sds->len。而C字符串为O(N)
   2、sds能防止缓冲区溢出。C字符串在调用strcat等操作时,并不会进行安全性检查,因此可能会导致缓冲区溢出。而sds在操作前都会对缓冲区进行检查
   3、减少修改字符串时带来的内存重分配的次数
   4、二进制安全,C字符串遇到空格即认为字符串结束,因此C字符串只能保存不含空格的字符串。而sds字符串有一个长度字段指示字符串的总长度,因此不会有这个限制

本文所引用的源码全部来自Redis3.0.7版本

redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值