redis7.x源码分析:(1) sds动态字符串

sds(Simple Dynamic String)是redis中最基础也是最重要的数据结构之一,其内部使用的key、协议、回复等等都会用它来存储。sds主要设计被用来替代C原生字符串 char *(数组),以便更便捷、更高效、更安全的进行字符串操作管理。其实它和C++标准库中的string在一定程度上是比较类似的,都是用来完成对字符串缓冲区的动态分配、管理以及其它一些相应操作的。

// sds的定义:
typedef char *sds;

sds的定义非常简单,直接就是一个char*的别名,因此sds本身具备C字符串的特性,可以使用strcpy、strlen等函数。 sds相关数据结构中真正重要的是sdshdr的定义,最初老版本的定义如下:

struct sdshdr {
    int len;    // SDS字符串的长度
    int free;   // 未使用的空间大小
    char buf[]; // 字符串数据
};

现在的sdshdr已经重新定义成5个不同的结构了:

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

这么定义的主要目的是节省存储空间,针对不同的字符串长度使用不同的头。另外结构中新增了flags标记,用来表示使用的是哪个头。如果flags类型是SDS_TYPE_5则高5bit还表示数据长度,因为sdshdr5中并没有长度的成员定义。 sds在分配空间时,是包含头结构的,但真正返回的却是buf成员的地址,这就是sds具备C字符串的特性的原因。由于结构体中设置了 attribute((packed)),表示按单字节对齐,因此可以通过sds[- 1]来获取flags的值,而后获取对应的结构体指针。

#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

// 获取SDS_TYPE_16对应的结构体指针
struct sdshdr16 *hdr = SDS_HDR(16,s)

接下来先看一下sds分配释放的主要实现 _sdsnewlen和 sdsfree,对外提供的分配函数最终都会调用它来实现。

sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;
    sds s;
    // 根据数据长度获取合适的结构体类型
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    // inilen为0时, 升级类型预留空间
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */
    size_t usable;

    // 分配: 头 + 数据长度 + 1的空间
    assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    if (sh == NULL) return NULL;
    // init不为空并且不是SDS_NOINIT, 则重置内存为0
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    // 对外返回的 sds 指针位置, 向后偏移头大小
    s = (char*)sh+hdrlen;
    // 存储flags的指针位置
    fp = ((unsigned char*)s)-1;
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    // 根据类型,设置结构体中相应的值
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    // 拷贝需要初始化的内容
    if (initlen && init)
        memcpy(s, init, initlen);
    // buf末尾赋0
    s[initlen] = '\0';
    return s;
}

......

void sdsfree(sds s) {
    if (s == NULL) return;
    // 释放时,需要把指针重定向到相应结构体的起始位置
    s_free((char*)s-sdsHdrSize(s[-1]));
}

下面再看下扩容的函数 _sdsMakeRoomFor,它的实现也非常清晰,内部的一些扩容操作都会调用它。

sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    // 获取剩余空间大小
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    // 获取当前type
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    // 空间够用则直接退出
    if (avail >= addlen) return s;

    len = sdslen(s);
    // 获取sds结构体分配内存的起始地址
    sh = (char*)s-sdsHdrSize(oldtype);
    // 新的需要分配的空间大小
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (greedy == 1) {
        // greedy为1时需要预留空间, 如果新分配空间小于1MB, 则分配空间调整为2倍大小; 如果大于1MB则分配成 + 1MB大小
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    // 按新长度获取新的type
    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    if (oldtype==type) {
        // 如果类型不变,则直接按照新大小realloc
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        // 如果类型变化了, 则重新分配内存并拷贝原来的数据到新内存以及释放原来的内存
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        // 设置flags
        s[-1] = type;
        // 设置新的长度
        sdssetlen(s, len);
    }
    // 设置可用空间大小
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

另外,sds在设计中本身也是二进制安全的,而且sds会在末尾多分配1字节并且置'\0',用于防止一些字符串操作的越界问题。因此它除了用作字符串外,还可以作为二进制数据的存储buf,在redis内部也有着广泛用途。

全部评论

相关推荐

11-17 20:52
南昌大学 C++
1.530.二叉搜索树的最小绝对差:依然是利用二叉搜索树中序遍历得到的结点是有序的特性,对其进行递归中序遍历,并采用双指针法得到两个结点间的绝对差(中序遍历得到的有序数组中相邻两个结点间的绝对差一定是最小的,不需要再去与更远的结点进行比较,只需要比较两两相邻间的差值就够了),最后通过对绝对差进行不断更新就能获得最小绝对差。2.501.二叉搜索树中的众数: 递归中序遍历,双指针比较前后两个结点是否相等,同时更新maxValue,当count==maxValue时,将该元素加入到结果集中。如果count>maxValue,要注意更新maxValue的值,并清空结果集(失效)。注意递归过程中这些值都要是全局变量。3.236.二叉树的最近公共祖先:递归后序遍历,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//左右子树都不为空,说明找到了pq,此时root就是最近公共祖先&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(left&nbsp;!=&nbsp;NULL&nbsp;&amp;&amp;&nbsp;right&nbsp;!=&nbsp;NULL)&nbsp;return&nbsp;root;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//当一边找了了p/q,就直接返回这边的结点,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//当层层递归结束,要么会只返回这个结点(这个结点本身就是最近公共祖先)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//要么会与另外一支汇合,分别作为左右结点返回值,最后返回他们此时的根节点&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(left&nbsp;!=&nbsp;NULL&nbsp;&amp;&amp;&nbsp;right&nbsp;==&nbsp;NULL)&nbsp;return&nbsp;left;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;if(left&nbsp;==&nbsp;NULL&nbsp;&amp;&amp;&nbsp;right&nbsp;!=&nbsp;NULL)&nbsp;return&nbsp;right;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;return&nbsp;NULL;这两天被文本查询卡住了,有点烦躁,把权游八季的解说看完了,现在心静下来了,后面继续刷算法,搞完这个文本查询就开始学STL了。不要放弃!
点赞 评论 收藏
分享
1.&nbsp;C++中的构造函数和析构函数的作用是什么?2.&nbsp;什么是C++中的命名空间?如何使用?3.&nbsp;C++中的虚析构函数有什么作用?4.&nbsp;C++中如何实现抽象类和接口?5.&nbsp;什么是多态的静态绑定和动态绑定?6.&nbsp;C++中的默认参数是什么?如何使用?7.&nbsp;什么是C++中的强制类型转换?8.&nbsp;C++中如何使用std::vector和std::list的区别?9.&nbsp;什么是C++中的std::map和std::set?10.&nbsp;C++中的异常安全性分为哪几种级别?11.&nbsp;什么是C++中的内存对齐?12.&nbsp;C++中如何使用std::pair和std::tuple?13.&nbsp;C++中的friend类和friend函数有什么区别?14.&nbsp;C++中如何实现模板类?15.&nbsp;什么是C++中的类型推导(decltype)?16.&nbsp;C++中的智能指针如何防止内存泄漏?17.&nbsp;C++中如何使用std::shared_ptr和std::weak_ptr?18.&nbsp;C++中的std::mutex和std::lock_guard有什么区别?19.&nbsp;什么是C++中的线程安全容器?20.&nbsp;C++中如何实现条件变量的使用?21.&nbsp;什么是C++中的移动语义?22.&nbsp;C++中的std::function和函数指针有什么区别?23.&nbsp;C++中如何使用std::algorithm库?24.&nbsp;C++中的std::initializer_list是什么?25.&nbsp;C++中如何使用模板元编程?26.&nbsp;什么是C++中的类型特征(type&nbsp;traits)?27.&nbsp;C++中如何实现自定义的迭代器?28.&nbsp;C++中的std::unique_ptr和std::shared_ptr的使用场景是什么?29.&nbsp;C++中如何处理字符串和字符数组的区别?30.&nbsp;C++中如何使用std::string和C风格字符串?31.&nbsp;什么是C++中的析构函数的虚函数?32.&nbsp;C++中如何实现运算符重载的友元函数?33.&nbsp;C++中的std::array和C风格数组有什么区别?34.&nbsp;C++中如何使用范围for循环遍历容器?35.&nbsp;C++中的std::optional是什么,如何使用?嵌入式C++面经推荐大佬面经&nbsp;&nbsp;链接在下边&nbsp;&nbsp;c++/嵌入式面经专栏-牛客网 https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务