Redis源码解析(一)SDS简单字符串

1. SDS的定义

以下代码基于Redis3.0, 代码较为简单

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};
  • len: 保存的字符串的长度,已经使用的字节数
  • free: buf数组还可以使用的字节数
  • buf: 字符数组,用来保存字符串

在这里插入图片描述
在这里插入图片描述

2. SDS与C字符串的区别

1. 获取字符串长度的时间复杂度不同

  • C语言中如果想要获取一个字符串的长度,需要遍历整个字符串,直到遇到'\0' 为止,时间复杂度为O(n)
  • SDS中使用一个变量len记录了字符串的长度,因此获取字符串的时间复杂度为O(1)

2. 杜绝缓冲区溢出

  • C语言中对字符串进行追加操作可能会导致缓冲区溢出,如下图所示:
    在这里插入图片描述
    在这里插入图片描述

  • SDS中的字符串拼接操作会先判断数组空间是否足够,不够会进行扩容,不会发生溢出现象

sds sdscatlen(sds s, const void *t, size_t len) {
    struct sdshdr *sh;
    size_t curlen = sdslen(s);

    s = sdsMakeRoomFor(s,len);//扩容操作
    if (s == NULL) return NULL;
    sh = (void*) (s-(sizeof(struct sdshdr)));
    memcpy(s+curlen, t, len);
    sh->len = curlen+len;
    sh->free = sh->free-len;
    s[curlen+len] = '\0';
    return s;
}
//扩容操作
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;
}

在Redis中,字符串会被经常修改,如果每次修改都要进行扩容操作,会使得效率降低

3. 空间预分配

针对字符串增长操作

#define SDS_MAX_PREALLOC (1024*1024)  //1MB
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;//2倍扩容
    else
        newlen += SDS_MAX_PREALLOC;//加上1MB
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

    newsh->free = newlen - len;
    return newsh->buf;
}
  • 增加后的字符串的长度如果小于1MB,执行2倍扩容操作
  • 增加后的字符串的长度如果大于等于1MB, 容量增加1MB
  • 通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数

4. 惰性空间释放

针对字符串缩短操作
一个字符串变短时,原来分配的空间变多,如果不及时释放,可能会导致内存泄漏

//从字符串s中移除字符串cset中的字符
sds sdstrim(sds s, const char *cset) {
//s指向buf s-size_struct表示sh指向sds结构体的位置
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;//字符串s开始位置
    ep = end = s+sdslen(s)-1;//字符串s结束位置
    //strchr(cset, *sp): 查找sp字符是否出现在字符串cset中
    while(sp <= end && strchr(cset, *sp)) sp++;
    while(ep > start && strchr(cset, *ep)) ep--;
    len = (sp > ep) ? 0 : ((ep-sp)+1);//len表示删除cset中字符后的字符串长度
    
    if (sh->buf != sp) memmove(sh->buf, sp, len);//buf前面有字符出现在cset中,空间移动
    //修改结构体的属性
    sh->buf[len] = '\0';
    sh->free = sh->free+(sh->len-len);
    sh->len = len;
    return s;
}

上面的代码从原始字符串s中删除字符串集合cset中的字符,并且只是从两侧进行修剪,比如s="XXXaYYYBXYYY, cset="XY" 修剪后的结果为aYYYB

5. 二进制安全

  • C语言中字符串以空字符'\0'判断结束,因此存储的数据中不能含有空字符
  • SDS中可以使用空字符,因为SDS中是使用len属性判断字符串结束的

6. 兼容部分C字符串函数

SDS保存的数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节来容纳这个空字符,这是为了让那些保存文本数据的SDS可以重用一部分<string.h>库定义的函数

3. 总结

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值