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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SDS简单动态字符串)是Redis底层数据结构之一。它采用一段连续的内存空间来存储字符串,并具有一定的灵活性和扩展性。下面是一个展示SDS数据结构的例子: ``` typedef struct sdshdr { int len; // 已占用空间的长度 int free; // 剩余可用空间的长度 char buf[]; // 字符数组 } sdshdr; ``` 在这个例子中,我们可以看到SDS结构包含了`len`表示已占用空间的长度,`free`表示剩余可用空间的长度,以及`buf[]`表示字符数组。举一个实际的例子,如果我们要存储字符串"Redis",SDS会为其分配5字节的已使用长度,并为其分配5字节的可用空间长度。 与C字符串相比,SDS具有一些区别。C字符串的长度需要通过遍历整个字符串才能获得,而SDS可以直接通过`len`属性来获取字符串的长度。此外,C字符串是以空字符'\0'作为字符串的结束标志,而SDS通过`len`属性来确定字符串的长度,不依赖空字符。 在Redis中,C字符串主要应用在不需要对字符串值进行修改的地方,比如打印日志。而SDS主要应用在需要修改字符串值的地方,比如Redis的数据库中,包含字符串值的键值对在底层都是使用SDS来实现的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis底层数据结构——SDS](https://blog.csdn.net/weixin_39939725/article/details/110912176)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值