zoukankan      html  css  js  c++  java
  • Redis 3.0.4 简单动态字符串(sds)

     字符串是Redis最常见的结构,Redis没有用C中的字符串,是自己构建的simple dynamic string来表示字符串

     字符串的源码在sds.c/sds.h

    •  sds的基本结构
    struct sdshdr {
        unsigned int len; //len表示当前buf中已使用字节长度
        unsigned int free;//表示buf中未使用字节的长度
        char buf[];//字节数组,保存字符串
    };
    • SDS与字符串的区别

      1.常数复杂度获取字符串长度

        因为sds的结构中len表示当前buf已经使用的字节长度,所以获取长度是只需要返回sds->len,时间复杂度是o(1),而C语言中必须便利整个字符串,直到遇到结尾空字符为止,所以时间复杂度是o(n).

      2.杜绝缓冲区溢出

        因为C字符串不记录自身长度,所以字符串操作容易出现缓冲区溢出现象,sds的空间分配策略避免了缓冲区溢出的可能性。

      3.减少修改字符串时带来的内存重新分配

          因为C字符串的长度和底层数组的长度存在关联性 ,内存重分配需要执行系统调用,执行系统调用会从用户态陷入内核态,底层依旧是通过中断实现的,所以需要保持上下文,栈信息,这个过程有时不能被中断的,所以是一个很耗时的过程。

        redis使用了空间预分配和惰性空间释放两种策略,

        空间预留,就是在字符串增长操作中,当需要扩大空间时,会对sds分配额外的空间。额外分配的空间又修改之后sds的长度决定

          1.当修改之后的长度小于SDS_MAX_PREALLOC(1M),那么分配的空间为len*2;

          2.当修改之后的长度大于SDS_MAX_PREALLOC(1M),那么分配的空间为len+1M;

          关于分配空间源码调用的事zrealloc底层调用的事realloc,如果原先的内存尾部有足够大的空间,则直接在原来的内存块尾部新增内存,如果原来的内存块尾部内存不足,或者原先的内存块无法改变大小,realloc将重新分配另一块size大小的内存,并把原来那块内存内容复制到新的内存块上。可参考:C—动态内存分配之malloc与realloc的区别

    /* Enlarge the free space at the end of the sds string so that the caller
     * is sure that after calling this function can overwrite up to addlen
     * bytes after the end of the string, plus one more byte for nul term.
     *
     * Note: this does not change the *length* of the sds string as returned
     * by sdslen(), but only the free buffer space we have. */
    //判断sds剩余空间是否满足addlen长度
    sds sdsMakeRoomFor(sds s, size_t addlen) {
        struct sdshdr *sh, *newsh;
        size_t free = sdsavail(s); //返回s未使用空间长度
        size_t len, newlen;
    
        if (free >= addlen) return s;
        //需要扩大空间,如果修改之后的长度小于SDS_MAX_PREALLOC,则分配2*len
        //如果大于SDS_MAX_PREALLOC,则再加上SDS_MAX_PREALLOC
        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;
    }

        2.惰性空间释放  

          用于优化sds字符串缩短操作,当缩短字符串时,并不会使用内存重新分配来回收缩短后多出来的字节,而是使用free属性将这些字节记录下来,待将来使用。

    //出去sds字符串中左右两端字符中包含cset字符的字符
    sds sdstrim(sds s, const char *cset) {
        struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
        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 > start && strchr(cset, *ep)) ep--;
        len = (sp > ep) ? 0 : ((ep-sp)+1);
        if (sh->buf != sp) memmove(sh->buf, sp, len);
        sh->buf[len] = '';
        sh->free = sh->free+(sh->len-len);
        sh->len = len;
        return s;
    }

    这块是字符串缩短的代码,找了一些资料都是说是去除sds字符串中cset中包含的所有字符,但是我看源码理解的是去除sds字符中首尾中包含cset中包含的字符,比如

     * Example:
     *
     * s = sdsnew("AA...AA.a.aa.Hello.World:::");
     * s = sdstrim(s,"Aa. :");
     * printf("%s
    ", s);
     * OutPut:Hello.World
     * 字符串中的.是去不掉的,然后求解~

      4.二进制安全

        sds用二进制的方式来处理存放在buf数组中的数据,程序不会对数据做任何限制、过滤、或者假设,数据写入是什么样的,读取时就是什么样。缓解了C语言只能存储文本文件的尴尬,sds不仅可以保存文本数据, 还可以保存任意格式的二进制数据,并且用len长度作为字符串结尾的约束。

      5.兼容部分C字符串函数

        虽然sds都是二进制安全的,但是还是遵循C字符串一空字符结尾,这是为了那些保存文本文件的sds可以重用一部分<string.h>库定义的函数。 

    C 字符串和 SDS 之间的区别

    C 字符串SDS
    获取字符串长度的复杂度为 O(N) 。 获取字符串长度的复杂度为 O(1) 。
    API 是不安全的,可能会造成缓冲区溢出。 API 是安全的,不会造成缓冲区溢出。
    修改字符串长度 N 次必然需要执行 N 次内存重分配。 修改字符串长度 N 次最多需要执行 N 次内存重分配。
    只能保存文本数据。 可以保存文本或者二进制数据。
    可以使用所有 <string.h> 库中的函数。 可以使用一部分 <string.h> 库中的函数。

       

  • 相关阅读:
    ubuntu12.04.2上利用cmake安装opencv2.4.6
    微软无线鼠标3500滚轮问题
    Linux 安装 Tomcat
    Linux 安装 MySQL
    Linux 安装 JDK
    Linux 安装 Redis 及踩坑
    IDEA Git 撤销push(回退到指定版本)
    【Java】Ajax实现级联城市
    node.js更换镜像源
    【Java】数据库连接池的简单使用
  • 原文地址:https://www.cnblogs.com/chenyang920/p/13122041.html
Copyright © 2011-2022 走看看