Redis源码剖析——SDS的实现

Redis中的SDS(简单动态字符串)是基础数据结构之一,具有高效的长度获取和内存管理。本文详细介绍了SDS的结构,包括sdshdr结构体、基本操作函数如sdsnewlen、sdslen、sdsavail等,并探讨了其内存扩展策略。

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

SDS的实现


SDS即简单动态字符串,为Redis的几大基本数据结构之一,有广泛的用途

基本函数总览

函数功能复杂度
sdsnewlen由给定字符串创建SDSO(N)
sdslen返回字符串长度O(1)
sdsdup复制一个SDSO(N)
sdsfree释放字符串空间O(1)
sdsavail获取空闲空间大小O(1)
sdsMakeRoomFor扩展SDS buf空间O(N)
sdsgrowzero扩展字符串到指定长度,多的用0填充O(N)
sdscatlen拼接字符串到已有字符串末尾O(N)
sdscpylen将字符串拷贝到现有SDS处O(N)

sdshdr结构体

/*
 * 类型别名,用于指向 sdshdr 的 buf 属性
 */
typedef char *sds;

/*
 * 保存字符串对象的结构,显然32位下 sizeof(sdshdr) = 8
 */
struct sdshdr {    
    // buf 中已占用空间的长度
    int len;
    // buf 中剩余可用空间的长度
    int free;
    // 数据空间
    char buf[];
};

sds的结构如图

这里写图片描述

sdsnewlen

/* 
 * 创建init指向的长度为 initlen 的字符串
 */
sds sdsnewlen(const void *init, size_t initlen) {

    struct sdshdr *sh;

    // 根据是否有初始化内容,选择适当的内存分配方式
    // T = O(N)
    if (init) {
        // zmalloc 不初始化所分配的内存
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // zcalloc 将分配的内存全部初始化为 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }

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

    // 设置初始化长度
    sh->len = initlen;
    // 新 sds 不预留任何空间
    sh->free = 0;
    // 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
    // T = O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // 以 \0 结尾
    sh->buf[initlen] = '\0';

    // 返回 buf 部分,而不是整个 sdshdr
    return (char*)sh->buf;
}

sdslen

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

sdsdup

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

sdsfree

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

zfree 在之前内存分配中已介绍

sdsavail

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

sdsMakeRoomFor

/*
 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后
 * buf 至少会有 addlen + 1 长度的空余空间
 * 返回值sds :扩展成功返回扩展后的 sds,扩展失败返回 NULL
 */
 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(1024 * 1024)
        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;
}

sdsgrowzero

/*
 * 将 sds 扩充至指定长度,未使用的空间以 0 填充,如果给定长度比已有长度小,直接返回
 * 返回值sds :扩充成功返回新 sds ,失败返回 NULL
 */
sds sdsgrowzero(sds s, size_t len) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    size_t totlen, curlen = sh->len;

    // 如果 len 比字符串的现有长度小,
    // 那么直接返回,不做动作
    if (len <= curlen) return s;

    s = sdsMakeRoomFor(s,len-curlen);
    // 如果内存不足,直接返回
    if (s == NULL) return NULL;

    /* Make sure added region doesn't contain garbage */
    // 将新分配的空间用 0 填充,防止出现垃圾内容
    // T = O(N)
    sh = (void*)(s-(sizeof(struct sdshdr)));
    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */

    // 更新属性
    totlen = sh->len+sh->free;
    sh->len = len;
    sh->free = totlen-sh->len;

    // 返回新的 sds
    return s;
}

sdscatlen

/*  
 * 拼接新字符串到已有字符串末尾
 */
sds sdscatlen(sds s, const void *t, size_t len) {

    struct sdshdr *sh;

    // 原有字符串长度
    size_t curlen = sdslen(s);

    // 扩展 sds 空间
    // T = O(N)
    s = sdsMakeRoomFor(s,len);

    // 内存不足?直接返回
    if (s == NULL) return NULL;

    // 复制 t 中的内容到字符串后部
    sh = (void*) (s-(sizeof(struct sdshdr)));
    memcpy(s+curlen, t, len);

    // 更新属性
    sh->len = curlen+len;
    sh->free = sh->free-len;

    // 添加新结尾符号
    s[curlen+len] = '\0';

    // 返回新 sds
    return s;
}

sdscpylen

sds sdscpylen(sds s, const char *t, size_t len) {

    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    // sds 现有 buf 的长度
    size_t totlen = sh->free+sh->len;

    // 如果 s 的 buf 长度不满足 len ,那么扩展它
    if (totlen < len) {
        // T = O(N)
        s = sdsMakeRoomFor(s,len-sh->len);
        if (s == NULL) return NULL;
        sh = (void*) (s-(sizeof(struct sdshdr)));
        totlen = sh->free+sh->len;
    }

    // 复制内容
    // T = O(N)
    memcpy(s, t, len);

    // 添加终结符号
    s[len] = '\0';

    // 更新属性
    sh->len = len;
    sh->free = totlen-len;

    // 返回新的 sds
    return s;
}

小结

在一段连续内存中,前面存储sdshdr 头部,包含了字符串长度和空闲空间大小,后面存储具体的字符串。这样获取字符串长度为O(1),实现简单,具有借鉴意义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值