redis 源码学习之动态字符串

本文详细解析了Redis的SDS数据结构,包括其设计理念、内存管理策略、如何进行预分配和惰性空间释放,以及与传统C字符串的兼容性。重点介绍了sds的创建、释放、动态调整和连接操作,展示了紧凑设计和高效性能。
摘要由CSDN通过智能技术生成

字符串数据结构总结

Redis没有使用C语言的字符串结构,而是自己设计了一个简单的动态字符串结构sds。它的特点是:可动态扩展内存、二进制安全和与传统的C语言字符串类型兼容

  1. sds数据结构定义
    Redis 专门设计了 SDS 数据结构,在字符数组
    的基础上,增加了字符数组长度和分配空间大小等元数据

typedef char *sds;
struct sds{
int free; //保存未使用的长度空间
int len; //保存已经分配的长度空间,不包括’\0’,
}

struct sdshdr{
    int len;//记录buf数组中已经使用字节的数量,等于SDS所保存字符串的长度
    int free;//记录buf数组中未使用字节的数量
    char buf[];
};

分析:sizeof(sdshdr) = 8,这时因为sizeof(int) = 4;空数组不占用内存空间

优点:

  • 常数复杂度获取字符串的长度
  • 杜绝缓冲溢出
  • 减少修改字符串时带来的内存分配次数(通过空间预分配和懒性空间释放实现的)
  • 怎么进行预分配?
    如果对SDS进行修改后,SDS的长度将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS len 属性的值将和free属性的值相同
    如果对SDS进行修改后,SDS的长度将大于1MB,那么程序分配1MB的未使用空间
  • 怎么进行惰性空间释放
    当SDS 的API 需要缩短SDS 保存的字符串时,程序并不立即使用内存重分配来回收缩短多出来的字节,而是使用free属性将这些字节的数量记录下来,并等待将来使用
  • 二进制安全的:所有的SDS API 都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf 数组里面的数据,程序不会对其中的数据做任何限制、过滤;判断字符串是否结束通过len判断
  • 兼容部分C字符串函数
  • 区别如下;
    在这里插入图片描述
  • sds 基本函数
    Redis在创建sds时,会为其申请一段连续的内存空间
  • sds 创建函数
    参数:
    init :初始化字符串指针
    initlen :初始化字符串的长度
    返回值:
    sds :创建成功返回 sdshdr 相对应的 sds 创建失败返回 NULL
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;
}
  • sds 释放函数
    sds的释放采用zfree来释放内存
void sdsfree(sds s) {
    if (s == NULL) return; 
    // 得到内存的真正其实位置,然后释放内存
    s_free((char*)s-sdsHdrSize(s[-1]));
}
  • sds 动态调整函数
    sds最重要的性能就是动态调整
    对 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;
}
  • sds 回收空余空间
    回收 sds 中的空闲空间,
    回收不会对 sds 中保存的字符串内容做任何修改。
    返回值 : sds :内存调整后的 sds
    复杂度: T = O(N)
// 用来回收sds空余空间,压缩内存,函数调用后,s会无效
// 实际上,就是重新分配一块内存,将原有数据拷贝到新内存上,并释放原有空间
// 新内存的大小比原来小了alloc-len大小
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;
}
  • sds连接操作函数
    sds提供了字符串的连接函数,用来连接两个字符串
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;
}

当然sds 还提供了很多其他的函数

学习的编程思想:

  • 使用元数据记录字符串数组长度和封装操作的设计思想
  • 节省内存(紧凑):
    1‘、在redis 4.0后做了一个优化:就是设计不同的header结构,为了容纳不同长度的字符串,这也可以达到节省空间的目的
    2、设置struct attribute ((packed)) sdshdr8,采用紧凑型,节约内存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值