zoukankan      html  css  js  c++  java
  • redis 5.0.2 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c

    一、数据结构

    redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构

     1 typedef char *sds;
     2 
     3 /* Note: sdshdr5 is never used, we just access the flags byte directly.
     4  * However is here to document the layout of type 5 SDS strings. */
     5 struct __attribute__ ((__packed__)) sdshdr5 {
     6     unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
     7     char buf[];
     8 };
     9 struct __attribute__ ((__packed__)) sdshdr8 {
    10     uint8_t len; /* used */
    11     uint8_t alloc; /* excluding the header and null terminator */
    12     unsigned char flags; /* 3 lsb of type, 5 unused bits */
    13     char buf[];
    14 };
    15 struct __attribute__ ((__packed__)) sdshdr16 {
    16     uint16_t len; /* used */
    17     uint16_t alloc; /* excluding the header and null terminator */
    18     unsigned char flags; /* 3 lsb of type, 5 unused bits */
    19     char buf[];
    20 };
    21 struct __attribute__ ((__packed__)) sdshdr32 {
    22     uint32_t len; /* used */
    23     uint32_t alloc; /* excluding the header and null terminator */
    24     unsigned char flags; /* 3 lsb of type, 5 unused bits */
    25     char buf[];
    26 };
    27 struct __attribute__ ((__packed__)) sdshdr64 {
    28     uint64_t len; /* used */
    29     uint64_t alloc; /* excluding the header and null terminator */
    30     unsigned char flags; /* 3 lsb of type, 5 unused bits */
    31     char buf[];
    32 };

      定义结构体时,加上了 __attribute__ ((__packed__)) 关键字,用于取消字节对齐,使其按照紧凑排列的方式,占用内存。这样做的目的并不仅仅只是为了节约内存的使用。结构体最后有一个 char buf[],查了资料之后了解到,其只是定义一个数组符号,并没有任何成员,不占用结构体的内存空间,其真实地址紧随结构体之后,可实现变长结构体。由此可以只根据sds字符串的真实地址,取到sds结构体的任意成员变量数据。如flags:

    1 void func(const sds s)
    2 {
    3     unsigned char flags = s[-1];
    4 }

      这个flags,从源码的注释上看,其低三位用于表示sds类型,高五位是当sds类型为sdshdr5时,表明字符串长度的。对于非sdshdr5的类型,有专门的成员变量描述当前已使用的长度,及总buffer长度。

    sds类型:

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

    sds定义了两个宏,用于获取sds结构体首地址:

    1 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
    2 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

    由此可见sds结构体的大致结构为

     1 /*
     2 sdshdr5
     3 +--------+----...---+
     4 |00011000|abc     |
     5 +--------+----...---+
     6 |flags   |buf
     7 
     8 sdshdr8
     9 +--------+--------+--------+----...---+
    10 |00000011|00000011|00000001|abc     |
    11 +--------+--------+--------+----...---+
    12 |len     |alloc   |flags   |buf
    13 */

    二、相关操作 

    2.1 字符串长度

      sds的一些常规操作,如获取字符串长度、获取剩余buf长度等,都是其于以上操作,首先根据sds字符串地址获取其flags的值,根据flags低三位判断sds类型,接着使用宏SDS_HDR_VAR或SDS_HDR进行操作。如:

     1 #define SDS_TYPE_MASK 7   //0000,0111
     2 
     3 static inline size_t sdslen(const sds s) {
     4 //获取flags
     5     unsigned char flags = s[-1];
     6 //根据flags低三位取类型,根据类型做不同处理
     7     switch(flags&SDS_TYPE_MASK) {
     8         case SDS_TYPE_5:
     9             return SDS_TYPE_5_LEN(flags);
    10         case SDS_TYPE_8:
    11             return SDS_HDR(8,s)->len;
    12         case SDS_TYPE_16:
    13             return SDS_HDR(16,s)->len;
    14         case SDS_TYPE_32:
    15             return SDS_HDR(32,s)->len;
    16         case SDS_TYPE_64:
    17             return SDS_HDR(64,s)->len;
    18     }
    19     return 0;
    20 }

      关于sds结构体中的len与alloc,len表示的是sds字符串的当前长度,alloc表示的是buf的总长度。

    2.2 字符串类型

      首先是一个根据字符串长度来决定sds类型的方法

     1 static inline char sdsReqType(size_t string_size) {
     2     if (string_size < 1<<5)    //flags高五位最大数字为 1<<5 - 1
     3         return SDS_TYPE_5;
     4     if (string_size < 1<<8)    //uint8_t 最大数字为 1<<8 - 1
     5         return SDS_TYPE_8;
     6     if (string_size < 1<<16)  //uint16_t 最大数字为 1<<16 - 1
     7         return SDS_TYPE_16;
     8 #if (LONG_MAX == LLONG_MAX)  //区分32位/64位系统
     9     if (string_size < 1ll<<32)
    10         return SDS_TYPE_32;
    11     return SDS_TYPE_64;
    12 #else
    13     return SDS_TYPE_32;
    14 #endif
    15 }

    2.3 字符串创建

      创建一个新的sds结构体:

     1 sds sdsnewlen(const void *init, size_t initlen) {
     2     void *sh;
     3     sds s;
     4     //根据长度获取sds类型
     5     char type = sdsReqType(initlen);
     6     /* Empty strings are usually created in order to append. Use type 8
     7      * since type 5 is not good at this. */
     8     /**
     9      * 如果初始长度为0的情况下,并且类型为SDS_TYPE_5,则会被强制转为SDS_TYPE_8
    10      */
    11     if (type == SDS_TYPE_5 && initlen == 0)
    12         type = SDS_TYPE_8;
    13 
    14     //除了buf[]变量其它变量所占的空间
    15     int hdrlen = sdsHdrSize(type);
    16     unsigned char *fp; /* flags pointer. */
    17 
    18     //sdshdrx(x:5、8、16、32、64)变量所占空间+字符串本身所占空间
    19     sh = s_malloc(hdrlen+initlen+1);
    20     if (init==SDS_NOINIT)
    21         init = NULL;
    22     else if (!init)
    23         memset(sh, 0, hdrlen+initlen+1);
    24     if (sh == NULL) return NULL;
    25     //获取buf[]数组符号
    26     s = (char*)sh+hdrlen;
    27     //得到flags变量
    28     fp = ((unsigned char*)s)-1;
    29     switch(type) {//根据相应类型设置类型(flag前3位)以及len、alloc
    30         case SDS_TYPE_5: {
    31             *fp = type | (initlen << SDS_TYPE_BITS);
    32             break;
    33         }
    34         case SDS_TYPE_8: {
    35             SDS_HDR_VAR(8,s);
    36             sh->len = initlen;
    37             sh->alloc = initlen;
    38             *fp = type;
    39             break;
    40         }
    41         case SDS_TYPE_16: {
    42             SDS_HDR_VAR(16,s);
    43             sh->len = initlen;
    44             sh->alloc = initlen;
    45             *fp = type;
    46             break;
    47         }
    48         case SDS_TYPE_32: {
    49             SDS_HDR_VAR(32,s);
    50             sh->len = initlen;
    51             sh->alloc = initlen;
    52             *fp = type;
    53             break;
    54         }
    55         case SDS_TYPE_64: {
    56             SDS_HDR_VAR(64,s);
    57             sh->len = initlen;
    58             sh->alloc = initlen;
    59             *fp = type;
    60             break;
    61         }
    62     }
    63     if (initlen && init)
    64         memcpy(s, init, initlen);//将字符串拷贝到buf[]中
    65     //指针指向的字符串节位处理
    66     s[initlen] = '';
    67     return s;
    68 }

      由外部指定初始字符串与初始长度先根据长度获取sds类型,然后根据不同类型,可以获得实际需要的总内存空间大小(包括sds结构体长度)。值得注意的是,如果初始长度为0的情况下,若为SDS_TYPE_5,则会被强制转为SDS_TYPE_8根据源码的注释,空串的定义,通常是为了向后追加内容。SDS_TYPE_5并不适合这种场景。分配完内存空间之后,设置好sds结构体的值,再把初始字符串拷至sds字符串的实际初始位置上(如果有),就可以了。

    2.4 字符串初始化  

      本方法做为最底层的sds字符串初始化接口,被其它接口所调用,如:

     1 //空string
     2 sds sdsempty(void) {
     3     return sdsnewlen("",0);
     4 }
     5 
     6 //指定string
     7 sds sdsnew(const char *init) {
     8     size_t initlen = (init == NULL) ? 0 : strlen(init);
     9     return sdsnewlen(init, initlen);
    10 }
    11 
    12 //从现有sds string拷贝
    13 sds sdsdup(const sds s) {
    14     return sdsnewlen(s, sdslen(s));
    15 }

    2.5 字符串释放

      sds的释放也不是简单地free sds字符串,同样,它要先找到sds结构体的首地址,再进行free:

    1 void sdsfree(sds s) {
    2     if (s == NULL) return;
    3     s_free((char*)s-sdsHdrSize(s[-1]));
    4 }

    2.6 字符串扩容

      做为一个变长字符串,与传统c字符串,最大的区别,是可以动态扩展,就像c++ stl里的变长数组 vector一样。sds的扩容有自己的机制

     1 sds sdsMakeRoomFor(sds s, size_t addlen) {
     2     void *sh, *newsh;
     3 
     4     //获取剩余可用空间
     5     size_t avail = sdsavail(s);
     6     size_t len, newlen;
     7     //获取原来的类型
     8     char type, oldtype = s[-1] & SDS_TYPE_MASK;
     9     int hdrlen;
    10 
    11     /* Return ASAP if there is enough space left. 如果剩余空间大于等于新增加的长度*/
    12     if (avail >= addlen) return s;
    13 
    14     //得到原来的字符串的长度
    15     len = sdslen(s);
    16 
    17     //获取sdshdrx类型的变量
    18     sh = (char*)s-sdsHdrSize(oldtype);
    19     //计算新字符串的长度
    20     newlen = (len+addlen);
    21     if (newlen < SDS_MAX_PREALLOC)  //1024*1024
    22         newlen *= 2;    //扩大两倍
    23     else
    24         newlen += SDS_MAX_PREALLOC;
    25 
    26     //根据新长度获取字符串变量的新类型
    27     type = sdsReqType(newlen);
    28 
    29     /* Don't use type 5: the user is appending to the string and type 5 is
    30      * not able to remember empty space, so sdsMakeRoomFor() must be called
    31      * at every appending operation.
    32      * 不要使用类型 5:用户附加到字符串并且类型 5 无法记住空格,因此必须在每次附加操作时调用 sdsMakeRoomFor()。
    33      *  */
    34     if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    35 
    36     hdrlen = sdsHdrSize(type);
    37     if (oldtype==type) {//如果前后类型相同
    38         newsh = s_realloc(sh, hdrlen+newlen+1);
    39         if (newsh == NULL) return NULL;
    40         s = (char*)newsh+hdrlen;
    41     } else {
    42         /* Since the header size changes, need to move the string forward,
    43          * and can't use realloc
    44          * 由于头部大小发生变化,需要将字符串向前移动,不能使用realloc
    45          */
    46         newsh = s_malloc(hdrlen+newlen+1);
    47         if (newsh == NULL) return NULL;
    48         //拷贝内容
    49         memcpy((char*)newsh+hdrlen, s, len+1);
    50         s_free(sh);
    51         //指向buf[]
    52         s = (char*)newsh+hdrlen;
    53         //设置类型,即flags
    54         s[-1] = type;
    55         //设置字符串长度
    56         sdssetlen(s, len);
    57     }
    58     //设置总的容量,alloc
    59     sdssetalloc(s, newlen);
    60     return s;
    61 }

    2.6.1 获取当前字符串的剩余空间

      本方法用于扩容sds,并可以指定长度。首先其先取出了当前还空闲的buf长度,方法如下:

     1 /**
     2  * 返回当前sds字符串剩余可用空间
     3  */
     4 static inline size_t sdsavail(const sds s) {
     5     unsigned char flags = s[-1];
     6     switch(flags&SDS_TYPE_MASK) {
     7         case SDS_TYPE_5: {
     8             return 0;
     9         }
    10         case SDS_TYPE_8: {
    11             SDS_HDR_VAR(8,s);
    12             return sh->alloc - sh->len;
    13         }
    14         case SDS_TYPE_16: {
    15             SDS_HDR_VAR(16,s);
    16             return sh->alloc - sh->len;
    17         }
    18         case SDS_TYPE_32: {
    19             SDS_HDR_VAR(32,s);
    20             return sh->alloc - sh->len;
    21         }
    22         case SDS_TYPE_64: {
    23             SDS_HDR_VAR(64,s);
    24             return sh->alloc - sh->len;//总长度-当前长度
    25         }
    26     }
    27     return 0;
    28 }

    2.6.2 扩容判断 

        若当前空闲的长度,比需要的长度大,则认为不用再额外分配空间,直接return。否则就启用扩容操作

      扩容时,先根据当前已使用的长度len与需要增加的长度addlen,算出一个初始新长度newlen,然后对其进行判断,若newlen大于1M,则在newlen的基础上,继续增加1M,否则直接翻倍。然后再根据newlen的最终大小,获取sds的新类型。此时,若类型依然为SDS_TYPE_5,也要强行修正为SDS_TYPE_8。因为SDS_TYPE_5类型并不知道当前空闲空间的大小。此时,若sds的新类型与原来相同,则只需要调用realloc重新分配一下空间即可。此方法会分配出一块新空间的同时,把原来空间的内容拷过去,并释放原有空间。而sds类型发生改变的时候,就需要手动新造一个新的sds了。扩容完成之后,需要修正一下当前已使用的空间len与总buf大小 alloc。

    2.7 字符串缩容

      扩容完成之后,或者是其它什么操作,如人工修改了sds字符串,并更新的len的情况下,会存在空闲空间太大的情况。此时如果想释放这部分空间,sds也提供了相应的操作

     1 sds sdsRemoveFreeSpace(sds s) {
     2     void *sh, *newsh;
     3     char type, oldtype = s[-1] & SDS_TYPE_MASK;
     4     int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
     5     size_t len = sdslen(s);
     6     sh = (char*)s-oldhdrlen;
     7 
     8     /* Check what would be the minimum SDS header that is just good enough to
     9      * fit this string. */
    10     type = sdsReqType(len);
    11     hdrlen = sdsHdrSize(type);
    12 
    13     /* If the type is the same, or at least a large enough type is still
    14      * required, we just realloc(), letting the allocator to do the copy
    15      * only if really needed. Otherwise if the change is huge, we manually
    16      * reallocate the string to use the different header type. */
    17     if (oldtype==type || type > SDS_TYPE_8) {
    18         newsh = s_realloc(sh, oldhdrlen+len+1);
    19         if (newsh == NULL) return NULL;
    20         s = (char*)newsh+oldhdrlen;
    21     } else {
    22         newsh = s_malloc(hdrlen+len+1);
    23         if (newsh == NULL) return NULL;
    24         memcpy((char*)newsh+hdrlen, s, len+1);
    25         s_free(sh);
    26         s = (char*)newsh+hdrlen;
    27         s[-1] = type;
    28         sdssetlen(s, len);
    29     }
    30     sdssetalloc(s, len);
    31     return s;
    32 }

      操作与扩容类似,同样是会根据sds类型是否发生变化 ,来决定是使用realloc还是重新造一个sds。

    2.7 其他操作

      除此之外,sds还实现了一些转义、数据类型转换、一些类似c风格的字符串操作等。如:strcpy、strcat、strlen、strcmp等。只是其更加多样化,如sds的strcat实现,就可以支持类似printf的方式。如:

     1 /* Like sdscatprintf() but gets va_list instead of being variadic. */
     2 sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
     3     va_list cpy;
     4     char staticbuf[1024], *buf = staticbuf, *t;
     5     size_t buflen = strlen(fmt)*2;
     6 
     7     /* We try to start using a static buffer for speed.
     8      * If not possible we revert to heap allocation. */
     9     if (buflen > sizeof(staticbuf)) {
    10         buf = s_malloc(buflen);
    11         if (buf == NULL) return NULL;
    12     } else {
    13         buflen = sizeof(staticbuf);
    14     }
    15 
    16     /* Try with buffers two times bigger every time we fail to
    17      * fit the string in the current buffer size. */
    18     while(1) {
    19         buf[buflen-2] = '';
    20         va_copy(cpy,ap);
    21         vsnprintf(buf, buflen, fmt, cpy);
    22         va_end(cpy);
    23         if (buf[buflen-2] != '') {
    24             if (buf != staticbuf) s_free(buf);
    25             buflen *= 2;
    26             buf = s_malloc(buflen);
    27             if (buf == NULL) return NULL;
    28             continue;
    29         }
    30         break;
    31     }
    32 
    33     /* Finally concat the obtained string to the SDS string and return it. */
    34     t = sdscat(s, buf);
    35     if (buf != staticbuf) s_free(buf);
    36     return t;
    37 }
    38 
    39 /* Append to the sds string 's' a string obtained using printf-alike format
    40  * specifier.
    41  *
    42  * After the call, the modified sds string is no longer valid and all the
    43  * references must be substituted with the new pointer returned by the call.
    44  *
    45  * Example:
    46  *
    47  * s = sdsnew("Sum is: ");
    48  * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
    49  *
    50  * Often you need to create a string from scratch with the printf-alike
    51  * format. When this is the need, just use sdsempty() as the target string:
    52  *
    53  * s = sdscatprintf(sdsempty(), "... your format ...", args);
    54  */
    55 sds sdscatprintf(sds s, const char *fmt, ...) {
    56     va_list ap;
    57     char *t;
    58     va_start(ap, fmt);
    59     t = sdscatvprintf(s,fmt,ap);
    60     va_end(ap);
    61     return t;
    62 }

      这类函数其实就是仿照标准函数printf、strcmp等对sds的一个具体实现,理解的时候按照标准函数的执行方式去理解就好

    三、sds相比c的标准库优势

    1、相比于c标准库,获取字符串的len复杂读从O(N)降低到O(1),sds结构中存储了字符串的长度,所以类似strlen(str)的操作不会成为redis的性能瓶颈。
    2、在内存分配策略上,redis总是会尝试多分配一些空间,比如小于1MB的字符串,总是分配2倍内存空间,对于大于1MB的空间追加1MB冗余空间,这对于字符串操作(如strcat等)能减少重新内存分配的几率,提升运行性能。
    3、SDS总是安全的,sds总是会自动追加字符串结尾符号’’,有效防止溢出发生。
    4、惰性释放内存,改变原字符串时,标准库需要重新分配内存的复杂度为O(N),SDS最大为O(N),最优情况下无需重新分配内存空间。

    四、redis 5.0.2 下载链接

      http://download.redis.io/releases/redis-5.0.2.tar.gz

    五、源码阅读顺序参考

      https://blog.huangz.me/diary/2014/how-to-read-redis-source-code.html

    参考文章

    https://www.cnblogs.com/chinxi/p/12231940.html

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/14966913.html

  • 相关阅读:
    Docker常用命令操作记录
    第一个netty程序--时间服务
    zookeeper+dubbo配置
    通过IRBuilder新建LLVM IR
    TVM结构介绍
    /lib64/libc.so.6 错误导致的系统崩溃
    php 间歇性报 Segmentation fault
    Dell服务器安装OMSA管理工具
    【Selenium学习笔记】网页截图实践
    局域网内网机器上网实操
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/14966913.html
Copyright © 2011-2022 走看看