zoukankan      html  css  js  c++  java
  • Redis源码解析01: 简单动态字符串SDS

    Redis没有直接使用C字符串(以’’结尾的字符数组),而是构建了一种名为简单动态字符串( simple  dynamic  string, SDS)的抽象类型,SDS设计API实现对字符串的各种修改。

    1:SDS的定义

      在sds.h中,定义了结构体sdshdr表示SDS,其定义如下:

    struct sdshdr {  
        unsigned int len;  
        unsigned int free;  
        char buf[];  
    };  

      len记录SDS保存的字符串的长度(不包括末尾的'');free记录buf中未使用的字节数量(也不包括’‘);buf是字节数组,用于保存字符串。比如下面的例子:  

      

      结合上图,很好理解free,len,buf字段的意义

      sds也提供了查询sds实例free,len的接口,这些接口可以在O(1)复杂度的情况下查询字符串的长度、未使用空间。

    static inline size_t sdslen(const sds s)   
    {  
        struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));  
        return sh->len;  
    }  
      
    static inline size_t sdsavail(const sds s)   
    {  
        struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));  
        return sh->free;  
    }  

    2:SDS与C字符串的区别

            C字符串不记录自身的长度信息,获取一个C字符串的长度的时间复杂度为O(N)。SDS在len属性中直接记录了字符串的长度,所以获取一个SDS字符串长度的事件复杂度是O(1)。这确保获取字符串长度的工作不会成为Redis的性能瓶颈。即使对一个非常长的字符串键反复执行”strlen”命令,也不会对系统性能造成任何影响。

            C字符串不记录长度带来的另一个问题是容易造成缓冲区滋出。比如strcat函数将src字符串中的内容拼接到dest字符串的末尾, 如果dst的长度不足以容纳src,就会产生缓冲区滥出。

      与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS的API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。

    sds sdscatlen(sds s, const void *t, size_t len)   
    {  
        struct sdshdr *sh;  
        size_t curlen = sdslen(s);  
      
        s = sdsMakeRoomFor(s,len);  
        if (s == NULL) return NULL;  
        sh = (void*) (s-(sizeof(struct sdshdr)));  
        memcpy(s+curlen, t, len);  
        sh->len = curlen+len;  
        sh->free = sh->free-len;  
        s[curlen+len] = '';  
        return s;  
    }  
      
    sds sdscat(sds s, const char *t)   
    {  
        return sdscatlen(s, t, strlen(t));  
    }  

      sdscat是通过sdscatlen实现的,在sdscatlen中,首先用sdsMakeRoomFor保证SDS具有足够的空间(sdsMakeRoomFor的函数实现见下面),然后才是将字符串t追加到s中。其他所有修改SDS的API都会通过sdsMakeRoomFor保证缓冲区不会溢出。

    sds sdsMakeRoomFor(sds s, size_t addlen)   
    {  
        struct sdshdr *sh, *newsh;  
        size_t free = sdsavail(s);  
        size_t len, newlen;  
      
        if (free >= addlen) return s;  
        len = sdslen(s);  
        sh = (void*) (s-(sizeof(struct sdshdr)));  
        newlen = (len+addlen);  
        if (newlen < SDS_MAX_PREALLOC)  
            newlen *= 2;  
        else  
            newlen += SDS_MAX_PREALLOC;  
        newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);  
        if (newsh == NULL) return NULL;  
      
        newsh->free = newlen - len;  
        return newsh->buf;  
    }  

      其中,SDS_MAX_PREALLOC的值就是1024*1024,也就是1M。参数addlen表示需要扩容的长度。

      可以发现对SDS空间拓展的时候分两种情况:  

      如果对SDS进行修改之后,SDS的长度小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时SDS的 len属性的值将和free属性的值相同。

            如果对SDS进行修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。比如,如果进行修改之后,SDS的len将变成30MB,那么程序会分配1 MB的未使用空间,SDS的buf数组的实际长度将为30MB+1MB+1byte。

      通过在修改SDS之前调用sdsMakeRoomFor函数,确保不会出现溢出的问题。

      

  • 相关阅读:
    .NET core webApi 使用JWT验证签名
    sudo
    Mysql Error Code : 1436 Thread stack overrun
    Parallel World 4 – Parallel Task (1)
    SQLSTATE[HY000] [2002] Can't connect to local MySQL server
    Parallel World 3 – Parallel Task (2)
    Parallel World 5 – Concurrent Collections (1)
    Utime failed: Permission denied in Smarty/sysplugins/smarty_internal_template.php on line xxx
    Add Reference
    Javascript Tips
  • 原文地址:https://www.cnblogs.com/lovelaker007/p/8676101.html
Copyright © 2011-2022 走看看