Redis数据结构之简单动态字符串(SDS)

本文探讨了Redis中简单动态字符串(SDS)的数据结构,包括空间预分配策略、惰性释放机制,以及如何确保二进制安全。重点讲解了内存管理优化,如如何在小范围修改时高效扩展和在空间缩减时保持内存效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言的字符串是字符串长度+’\0’,这种形式使得获取数组长度的时间复杂度为 O ( n ) O(n) O(n)。而当想扩容的时候需要预先分配空间,一旦忘记分配,就会导致数组越界

简单动态字符串

Redis使用的是简单动态字符串(SDS),其定义如下:

struct sdshdr {
 // 用于记录数组长度
 int len;
 // 用于记录数组种未使用的字节数
 int free;
 // 用于保存字符串列表
 char buf[];
}

内存分配

其中Redis对于字符串修改导致的内存分配有一定的优化:
(1) 空间预分配

当修改的SDS长度len使用内存小于1MB的时候,会分配内存使得分配后SDS的len==free。如SDS的使用长度为14,分配后SDS的数组大小为14+14+1=29(一字节保存空字符)
当修改后SDS的len大于1MB,则会额外分配1MB的内存

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);
    /******** 这个公式很常见,是通过数组的地址取出整个SDS的地址 *****/
    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;
}

(2)惰性释放

当字符串空间被缩短时,不释放里面的空间,而是保留这部分内存,修改free的值。下次当字符串长度增加时,就不需要再次分配了。

例如下面的方法,仅仅是将数组清空,但是并没有释放内存

void sdsclear(sds s) {
    // 取出 sdshdr
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    // 重新计算属性
    sh->free += sh->len;
    sh->len = 0;
    // 将结束符放到最前面(相当于惰性地删除 buf 中的内容)
    sh->buf[0] = '\0';
}

如果想要释放内存,可以使用如下方法

sds sdsRemoveFreeSpace(sds s) {
    struct sdshdr *sh;
    sh = (void*) (s-(sizeof(struct sdshdr)));
    // 进行内存重分配,让 buf 的长度仅仅足够保存字符串内容
    // T = O(N)
    sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
    // 空余空间为 0
    sh->free = 0;
    return sh->buf;
}

二进制安全

另外Redis的字符串是二进制安全的。即对于存进来的数据,不做任务解析,修改或者假设。比如’\0’对于C语言的字符串是有特殊意义的,表示字符串结束的位置。这样的规则使得其无法存储音频,压缩文件等的二进制数据,只能用于存储文本数据。

Redis使用数组不是存储的是一系列二进制数据,而不是用于保存字符串。数据被写入时是怎么样,它被读取的时候就是怎么样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值