zoukankan      html  css  js  c++  java
  • Redis3.20阅读-SDS实现

    声明:这是本人参考黄建宏的《redis设计与实现》(源码版本是redis3.0)来学习redis3.20源码的笔记,如果有什么不对的地方,欢迎大家指正,大家一起学习、一起进步,QQ:499656254。

    一、SDS介绍

        SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:

    1、O(1)时间内获取字符串长度。(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)

    2、SDS提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)

    3、减少了修改字符串时带来的内存重分配次数。

          对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1

         对于减少的字符串其并不立即释放空间,而是回归到alloc中去。

    这个构建的结构在Redis3.20中的表示如下(和Redis2.x中还是有一定区别的):

    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[];
    };
    

     从代码中可以看出,SDS表示的字符串是有SDSheader和char*指针组成,而SDS的头部主要由四部分组成:

                      len:SDS字符串已使用的空间。

                      alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。

                      flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一                           部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。

                      buf:  用了C的特性表示不定长字符串。

    二、API学习

        1、sdsnewlen函数

              函数原型:sds sdsnewlen(const void *init, size_t initlen)

              说明:sdsnewlen用来创建init所指向对象作为内容的SDS,比如mystring = sdsnewlen("abc",3)。其中sdsnew函数也是调用sdsnewlen函数来实现的

              返回值:buf数组的指针位置

    sds sdsnewlen(const void *init, size_t initlen) {
        void *sh;
        sds s;
        char type = sdsReqType(initlen);//根据initlen的长度,选择不同的type,进一步来节省内存空间                                                                                                                                                        
        /* Empty strings are usually created in order to append. Use type 8
         * since type 5 is not good at this. */
        if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
        int hdrlen = sdsHdrSize(type);//返回sdshdr结构体大小
        unsigned char *fp; /* flags pointer. */
    
        sh = s_malloc(hdrlen+initlen+1);//底层调用malloc申请空间
        if (!init)
            memset(sh, 0, hdrlen+initlen+1);//若创建的sds对象为空,则空间赋值0
        if (sh == NULL) return NULL;//分配失败返回NULL
        s = (char*)sh+hdrlen;//指向buf数组
        fp = ((unsigned char*)s)-1;//指向flag
        switch(type) {//根据type不同对sdshdr结构体进行赋值,len和alloc设置为initlen
            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 = initlen;
                *fp = type;
                break;
            }
            case SDS_TYPE_16: {
                SDS_HDR_VAR(16,s);
                sh->len = initlen;
                sh->alloc = initlen;
                *fp = type;
                break;
            }
            case SDS_TYPE_32: {
                SDS_HDR_VAR(32,s);
                sh->len = initlen;
                sh->alloc = initlen;
                *fp = type;
                break;
            }
            case SDS_TYPE_64: {
                SDS_HDR_VAR(64,s);
                sh->len = initlen;
                sh->alloc = initlen;
                *fp = type;
                break;
            }
        }
        if (initlen && init)//将字符串拷贝至分配的内存空间
            memcpy(s, init, initlen);
        s[initlen] = '';
        return s;
    }
    

        2、sdsMakeRoomFor函数

            函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)

           说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。

           返回值:扩充后的sds对象

    sds sdsMakeRoomFor(sds s, size_t addlen) {
        void *sh, *newsh;
        size_t avail = sdsavail(s);//返回剩余可用空间,即s->alloc - s->len
        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) {    //若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配。
            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;
    }
    

      3、sdstrim函数

             函数原型:sds sdstrim(sds s, const char *cset)

             说明:从左右两边剔除sds对象包含集合CSET中的元素,内部通过memmove函数移位实现。

    sds sdstrim(sds s, const char *cset) {
        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 > sp && strchr(cset, *ep)) ep--;
        len = (sp > ep) ? 0 : ((ep-sp)+1);
        if (s != sp) memmove(s, sp, len);
        s[len] = '';
        sdssetlen(s,len);
        return s;
    }
    

    三、结论

        sds数据类型是redis里面常用的数据类型,所以其在设计优化上面有了一定的改动(相对于redis2.x版本),比如其数据结构发生了改变。最后想说下,其源码实现确实比较简单,但是代码写的很nice(至少目前的我还写不出来,不过我要加油)

  • 相关阅读:
    Avito Cool Challenge 2018:D. Maximum Distance
    Avito Cool Challenge 2018:C. Colorful Bricks
    Struts2开发基本步骤
    hibernate query.list() 返回的数据类型
    hibernate query.list() 返回的数据类型
    HibernateUtil工具类
    Hibernate实现步骤
    Hibernate实现步骤
    分页关键知识点总结
    详解
  • 原文地址:https://www.cnblogs.com/break-python/p/5506606.html
Copyright © 2011-2022 走看看