redis-简单动态字符串SDS

1、redis自定义了新的字符串机制SDS
    Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组),而是自己构建了一种名为简单动态字符串SDS的抽象类型
2、简单动态字符串SDS
   SDS定义:
struct sdshdr {
    // buf 中已占用空间的长度,不包括末尾‘\0’
    int len;
    // buf 中剩余可用空间的长度
    int free;
    // 数据空间,一个char类型的数组
    char buf[];
};

3、SDS与C字符串的区别

1)获取字符串长度的复杂度

    在C字符串中,C字符串是以一个数组来保存的(如上图所示),因此需要获取字符串的长度,需要遍历整个数组,时间复杂度为O(N);
    在SDS中,由于SDS结构体中保存了一个len变量,直接读取该变量即可知道字符串的长度,时间复杂度为O(1)
2)缓冲区溢出
    由于C字符串在保存之前需要为这个字符串数组分配大小,如果一直往这个数组插入元素,则会造成缓冲区溢出,当然如果要避免缓冲区溢出,则需要先检查数组是否被填满,如果是,则重新手动分配一个更大的空间,然后将数据拷贝过去,再释放原数组;
    SDS中,用户只需要往里面添加字符即可,看似不会再有缓冲区溢出,其实只是将缓冲区溢出检查,重新分配内存,拷贝数据,释放原缓冲区这些操作交给幕后去执行了,而不用用户去执行这一部分工作(有点类似于STL中vector的扩容机制)
3)减小修改字符串时带来的内存重分配次数
C字符串的缺陷:
    执行增长字符串的操作时,程序需要先通过内存重分配来扩展底层数组的空间大小,否则可能会产生缓冲区溢出;
    执行缩短字符串的操作时,程序需要通过内存重分配来释放字符串不再使用的那部分空间,否则可能会产生内存泄漏
SDS的空间预分配, 以修改后len是否大于1M,有两种分配方式:
    如果修改后len长度将小于 1 M, 这时分配给free的大小和len一样, 例如修改过后为13字节,  那么给free也是13字节 .    buf实际长度变成了  13 byte+ 13byte + 1byte = 27byte;
    如果修改后len长度将大于等于1 M, 这时分配给free的长度为 1 M,     例如修改过后为30M,  那么给free是1M .    buf实际长度变成了  30M + 1M + 1 byte;
    在修改时, 首先检查空间是不是够, 如果足够, 直接使用, 否则执行内存重分配.
SDS惰性空间释放:
    程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free变量将这些字节的数量记录起来,病等待将来使用;        
    SDS也提供了相应的API,让我们可以在有需要的时候,真正释放SDS的未使用空间,所以不用担心惰性空间释放策略会造成内存泄漏
4)二进制安全
    C字符串只能保存文本数据,因为C字符串遇到‘\0’即认为结束;SDS则是通过len变量来判断字符串结束位置的,因此SDS可以保存文本或二进制数据
5)SDS仅可以使用部分C字符串函数
    SDS在字符串末尾添加‘\0’,主要是为了能够让那些保存文本数据的SDS可以重用一部分C字符串处理函数,而不用重写
4、扩容部分源码(具体的扩容机制):
/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 * 
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
/*
 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
 * buf 至少会有 addlen + 1 长度的空余空间
 * (额外的 1 字节是为 \0 准备的)
 *
 * 返回值
 *  sds :扩展成功返回扩展后的 sds
 *        扩展失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;

    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的长度
    newlen = (len+addlen);

    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC 
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

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

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}
参考书籍《redis设计与实现》


猜你喜欢

转载自blog.csdn.net/weixin_39138071/article/details/80110061