zoukankan      html  css  js  c++  java
  • Redis源码剖析(二)简单动态字符串

     Redis没有使用C语言的字符串结构,而是自己设计了一个简单的动态字符串结构sds。它的特点是:可动态扩展内存、二进制安全和与传统的C语言字符串类型兼容。下面就从源码的角度来分析一下Redis中sds的实现。

    1 SDS的定义

    // sds兼容传统C风格字符串,所以起了个别名叫sds,并且可以存放sdshdr结构buf成员的地址
    typedef char *sds; 

    SDS也有一个表头(header)用来存放sds的信息。

    /*
     * 保存字符串对象的结构
     */
    struct sdshdr {
        
        // buf 中已占用空间的长度
        int len;
    
        // buf 中剩余可用空间的长度
        int free;
    
        // 数据空间
        char buf[];
    };

    以下为一个SDS示例:

    len为5,表示这个sds长度为5字节。
    free为0,表示这个sds没有未使用的空间。
    buf是一个char[]的数组,分配了(len+1+free)个字节的长度,前len个字节保存着’R’、’e’、’d’、’i’、’s’这5个字符,接下来的1个字节保存着’\0’,剩下的free个字节未使用。

    2 SDS的优点

    2.1 杜绝缓冲区溢出

    因为SDS表头的free成员记录着buf字符数组中未使用空间的字节数,所以,在进行APPEND命令向字符串后追加字符串时,如果不够用会先进行内存扩展,在进行追加。

    总之,正是因为表头的存在,使得redis的字符串有这么多优点。

    2.2 获得字符串长度的操作复杂度为O(1)

    传统的C字符串获得长度时的做法:遍历字符串的长度,遇零则止,复杂度为O(n)。

    而SDS表头的len成员就保存着字符串长度,所以获得字符串长度的操作复杂度为O(1)。

    2.3 二进制安全(Binary Safe)

    因为传统C字符串符合ASCII编码,这种编码的操作的特点就是:遇零则止 。即,当读一个字符串时,只要遇到’\0’结尾,就认为到达末尾,就忽略’\0’结尾以后的所有字符。因此,如果传统字符串保存图片,视频等二进制文件,操作文件时就被截断了。

    3 SDS内存分配及释放策略

    3.1 SDS内存分配策略—空间预分配

    空间预分配策略用于优化SDS的字符串增长操作。

    如果对SDS进行修改后,SDS表头的len成员小于1MB,那么就会分配和len长度相同的未使用空间。free和len成员大小相等。
    如果对SDS进行修改后,SDS的长度大于等于1MB,那么就会分配1MB的未使用空间。
    通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数。

    源代码如下:

    sds sdsMakeRoomFor(sds s, size_t addlen) 
    {
        struct sdshdr *sh, *newsh;
    
        // 获取 s 目前的空余空间长度
        size_t free = sdsavail(s);
    
        size_t len, newlen;
    
        // s 目前的空余空间已经足够,无须再进行扩展,直接返回
        if (free >= addlen) return s;
    
        // 获取 s 目前已占用空间的长度
        len = sdslen(s);
        sh = (void*) (s-(sizeof(struct sdshdr)));
    
        // s 最少需要的长度
        newlen = (len+addlen);
    
        // 根据新长度,为 s 分配新空间所需的大小
        if (newlen < SDS_MAX_PREALLOC)
            // 如果新长度小于 SDS_MAX_PREALLOC 
            // 那么为它分配两倍于所需长度的空间
            newlen *= 2;
        else
            // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
            newlen += SDS_MAX_PREALLOC;
        // T = O(N)
        newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    
        if (newsh == NULL) return NULL;
    
        // 更新 sds 的空余长度
        newsh->free = newlen - len;
    
        return newsh->buf;
    }

    3.2 SDS内存释放策略—惰性空间释放

    惰性空间释放用于优化SDS的字符串缩短操作。

    当要缩短SDS保存的字符串时,程序并不立即使用内存充分配来回收缩短后多出来的字节,而是使用表头的free成员将这些字节记录起来,并等待将来使用。
    源代码如下:

    void sdsclear(sds s) 
    {
        // 取出 sdshdr
        struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    
        // 重新计算属性
        sh->free += sh->len;
        sh->len = 0;
    
        // 将结束符放到最前面(相当于惰性地删除 buf 中的内容)
        sh->buf[0] = '\0';
    }
  • 相关阅读:
    【进阶3-5期】深度解析 new 原理及模拟实现(转)
    ios 手机端 input 框上方有内阴影
    手机端上点击input框软键盘出现时把input框不被覆盖,显示在屏幕中间(转)
    xampp 搭建好本地服务器以后手机无法访问
    【进阶3-4期】深度解析bind原理、使用场景及模拟实现(转)
    阿里云播放器
    flex下部固定高,上部不固定,而且超过内容要滚动
    Vue项目笔记
    jQuery实现鼠标点击div外的地方div隐藏消失的效果(转)
    android WebView详细使用方法(转)
  • 原文地址:https://www.cnblogs.com/lizhimin123/p/9965815.html
Copyright © 2011-2022 走看看