美文网首页
reids string

reids string

作者: Wu杰语 | 来源:发表于2018-09-25 22:53 被阅读0次

    redis中支持的数据结构都是经过设计优化的数据结构和算法。

    1. redis string数据结构

    见sds.h

    typedef char *sds;
    
    /* 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
    #define SDS_TYPE_MASK 7
    #define SDS_TYPE_BITS 3
    

    string存类型或者字符串,如果是字符串,使用sds中这样的设计。

    类型的区别是长度的不同,根据不同的长度用不同的类型

    • sdshdr8 表示可以存储2^8 -1长度的字符串
    • sdshdr16 表示可以存储2^16 -1长度的字符串
    • sdshdr32 表示可以存储2^32 -1长度的字符串
    • sdshdr64 表示可以存储2^64 -1长度的字符串

    2. string操作

    sds sdsnewlen(const void *init, size_t initlen);
    sds sdsnew(const char *init);
    sds sdsempty(void);
    sds sdsdup(const sds s);
    void sdsfree(sds s);
    sds sdsgrowzero(sds s, size_t len);
    sds sdscatlen(sds s, const void *t, size_t len);
    sds sdscat(sds s, const char *t);
    sds sdscatsds(sds s, const sds t);
    sds sdscpylen(sds s, const char *t, size_t len);
    sds sdscpy(sds s, const char *t);
    
    void sdsclear(sds s);
    int sdscmp(const sds s1, const sds s2);
    sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
    void sdsfreesplitres(sds *tokens, int count);
    void sdstolower(sds s);
    void sdstoupper(sds s);
    sds sdsfromlonglong(long long value);
    sds sdscatrepr(sds s, const char *p, size_t len);
    sds *sdssplitargs(const char *line, int *argc);
    sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
    sds sdsjoin(char **argv, int argc, char *sep);
    sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
    
    /* Low level functions exposed to the user API */
    sds sdsMakeRoomFor(sds s, size_t addlen);
    void sdsIncrLen(sds s, ssize_t incr);
    sds sdsRemoveFreeSpace(sds s);
    size_t sdsAllocSize(sds s);
    void *sdsAllocPtr(sds s);
    
    /* Export the allocator used by SDS to the program using SDS.
     * Sometimes the program SDS is linked to, may use a different set of
     * allocators, but may want to allocate or free things that SDS will
     * respectively free or allocate. */
    void *sds_malloc(size_t size);
    void *sds_realloc(void *ptr, size_t size);
    void sds_free(void *ptr);
    
    

    3. redis string为何这么设计

    redis为何要设计一个这样的数据结构,这是最大的问题。C字符串不能满足要求码?如果要这么设计,肯定是在算法上有优化。

    《redis设计与实现》总讲解了:

    • 常数复杂度获取字符串长度。
    • 杜绝缓冲区溢出。
    • 减少修改字符串长度时所需的内存重分配次数。
    • 二进制安全。
    • 兼容部分 C 字符串函数。

    3.0 typdef char* sds

    typedef的最大作用是用来表达语义,在这里

    3.1 O(1)复杂度的读取

    这个很容易看出,在数据结构中的第一项,就是长度

    static inline size_t sdslen(const sds s) {
        unsigned char flags = s[-1];
        switch(flags&SDS_TYPE_MASK) {
            case SDS_TYPE_5:
                return SDS_TYPE_5_LEN(flags);
            case SDS_TYPE_8:
                return SDS_HDR(8,s)->len;
            case SDS_TYPE_16:
                return SDS_HDR(16,s)->len;
            case SDS_TYPE_32:
                return SDS_HDR(32,s)->len;
            case SDS_TYPE_64:
                return SDS_HDR(64,s)->len;
        }
        return 0;
    }
    

    3.2 杜绝缓冲器溢出

    sds sdscat(sds s, const char *t) {
        return sdscatlen(s, t, strlen(t));
    }
    
    sds sdscatlen(sds s, const void *t, size_t len) {
        size_t curlen = sdslen(s);
    
        s = sdsMakeRoomFor(s,len);
        if (s == NULL) return NULL;
        memcpy(s+curlen, t, len);
        sdssetlen(s, curlen+len);
        s[curlen+len] = '\0';
        return s;
    }
    
    sds sdsMakeRoomFor(sds s, size_t addlen) {
        void *sh, *newsh;
        size_t avail = sdsavail(s);
        size_t len, newlen;
        char type, oldtype = s[-1] & SDS_TYPE_MASK;
        int hdrlen;
    
        /* Return ASAP if there is enough space left. */
        if (avail >= addlen) return s;
    
        len = sdslen(s);
        sh = (char*)s-sdsHdrSize(oldtype);
        newlen = (len+addlen);
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    
        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);
        if (oldtype==type) {
            newsh = s_realloc(sh, hdrlen+newlen+1);
            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(hdrlen+newlen+1);
            if (newsh == NULL) return NULL;
            memcpy((char*)newsh+hdrlen, s, len+1);
            s_free(sh);
            s = (char*)newsh+hdrlen;
            s[-1] = type;
            sdssetlen(s, len);
        }
        sdssetalloc(s, newlen);
        return s;
    }
    
    static inline size_t sdsavail(const sds s) {
        unsigned char flags = s[-1];
        switch(flags&SDS_TYPE_MASK) {
            case SDS_TYPE_5: {
                return 0;
            }
            case SDS_TYPE_8: {
                SDS_HDR_VAR(8,s);
                return sh->alloc - sh->len;
            }
            case SDS_TYPE_16: {
                SDS_HDR_VAR(16,s);
                return sh->alloc - sh->len;
            }
            case SDS_TYPE_32: {
                SDS_HDR_VAR(32,s);
                return sh->alloc - sh->len;
            }
            case SDS_TYPE_64: {
                SDS_HDR_VAR(64,s);
                return sh->alloc - sh->len;
            }
        }
    
    static inline void sdssetlen(sds s, size_t newlen) {
        unsigned char flags = s[-1];
        switch(flags&SDS_TYPE_MASK) {
            case SDS_TYPE_5:
                {
                    unsigned char *fp = ((unsigned char*)s)-1;
                    *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
                }
                break;
            case SDS_TYPE_8:
                SDS_HDR(8,s)->len = newlen;
                break;
            case SDS_TYPE_16:
                SDS_HDR(16,s)->len = newlen;
                break;
            case SDS_TYPE_32:
                SDS_HDR(32,s)->len = newlen;
                break;
            case SDS_TYPE_64:
                SDS_HDR(64,s)->len = newlen;
                break;
        }
    }
    

    防溢出可以从sdscat看到,在拼接之前,会使用sdsMakeRoomFor (s, len)重新计算s,逻辑上,当可分配空间足够的时候,直接返回s;当不足够,需要扩展,但是没有改变类型的时候,就需要realloc空间,当改变了类型的时候,需要重新分配内存。

    3.3 减少修改字符串长度时所需的内存重分配次数

    • 预分配,见上面代码sdsMakeRoomFor,这里有内存分配
    • 惰性释放,主要体现在数据结构中的alloc和len,一开始alloc等于len,如果有释放,len会减少,alloc不会减少,当向sds中追加的时候,如果alloc-len足够大,就直接追加到该内存中了,不需要再另外分配内存了。

    3.4 二进制安全

    C字符串,只能存放字符串,不能存放中间有‘\0’的字符,而redis string的这种设计,可以保存,这就是二进制安全。

    4 小结

    redis的string数据结构设计,来实现redis string算法,在处理小字符串时的确没有什么优势,当存储超大数据的时候,它的优势就非常明显了,这种设计需要仔细体悟。

    相关文章

      网友评论

          本文标题:reids string

          本文链接:https://www.haomeiwen.com/subject/uevkoftx.html