redis设计与实现之简单动态字符串

介绍

redis在设计过程中没有传统的c语言字符串,而是构建了一种名为简单动态字符串的抽象类型,并将SDS用作redis的默认字符串表示。
在redis 内部,仅有部分日志打印的地方使用了传统的字符串

SDS的定义

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};
  1. 属性值len,表示这个SDS字符串代表的字符串的长度
  2. free 表示为分配未使用的字符串长度
  3. buf属性是一个char类型的字符。如果存在值在,则字符以\0结束。
    一个SDS字符串内存的实例

字符串创建

//实际创建SDS
sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
	//有初始化字符串事
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // 默认初始化为0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    //分配内存失败,退出
    if (sh == NULL) return NULL;
    //设置长度
    sh->len = initlen;
    //未分配未使用的长度
    sh->free = 0;
    //拷贝字符串
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // 保证buf 为c语言字符串
    sh->buf[initlen] = '\0';
    //返回的buf的地址,在后续使用的时候需要转换地址
    return (char*)sh->buf;
}
// 构建一个字符串
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

获取字符串长度

// o(1)时间复杂度
static inline size_t sdslen(const sds s) {
    //在上面创建字符串的时候可以发现,实际返回的是buf的地址,所以在求取长度的时候需要转为结构体sds的地址,将地址超前移动sizeof(struct sdshdr)即可
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

字符串重新分配 append命令实现

/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
 * end of the specified sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
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;
}

/* Append the specified null termianted C string to the sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

/* Append the specified sds 't' to the existing sds 's'.
 *
 * After the call, the modified sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}

/* redis sds字符串预分配机制的实现
 * 
 * 最多分配1M的内存,如果不超过1M 则double
 *
 * 不会改变字符串的长度,只是会预分配内存
 */
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);
    //SDS_MAX_PREALLOC 为1024 * 1024
    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;
}
// 获取分配未使用的内存
static inline size_t sdsavail(const sds s) {
    // 转换地址
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

缩短字符串

sds sdstrim(sds s, const char *cset) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s+sdslen(s)-1;
    while(sp <= end && strchr(cset, *sp)) sp++;
    while(ep > start && strchr(cset, *ep)) ep--;
    len = (sp > ep) ? 0 : ((ep-sp)+1);
    if (sh->buf != sp) memmove(sh->buf, sp, len);
    sh->buf[len] = '\0';
    sh->free = sh->free+(sh->len-len);
    sh->len = len;
    return s;
}

好处

  1. 兼容c字符串,c语言相关的方法都可以直接使用例如strlen strcpy strcat等
  2. 预分配内存,频繁修改内存带来的内存重新分配开销
  3. 惰性删除,在缩短字符串时保留预分配的内存,在添加内容可以直接使用
  4. 二进制安全,所有的读取都是以len为准则,而不是通过遍历字符串的方式实现的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值