大粨兔奶糖

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

redis 字符串

概述

redis 没有使用 c 语言风格的字符串表示(以 "\0" 作为结尾), 而是使用自定义的 sds 结构

字符串结构

定义位置 (src/sds.h)

// 类型别名, 用于指向 sdshdr 的 buf 属性
typedef char *sds;

// 字符串对象的结构
struct sdshdr {    
    // 字符串长度
    int len;
    // 字符串剩余可用空间的长度 (不包括 "\0")
    int free;
    /*
    * 数据空间
    * sds 指向的地址
    * 遵循以 "\0" 作为结尾, 这样对于 sds 变量可以使用 c 标准库的函数
    * 比如: printf("%s", sdshdr->buf)
    */
    char buf[];
};
  • len 字段

    • 字符串长度 (不包括 "\0")

    • 通过 len 长度来判断字符串的结尾, 二进制安全

    • 常数复杂度获取字符串长度

    • 若不设置 len 长度, 会存在缓冲区溢出问题, 如: strcat 函数, 此函数假定已经为字符串连接留下了空间, 一旦假定不成立, 就会缓冲区溢出

      sds 结构完全杜绝了缓冲区溢出的问题, 在进行字符串操作的时候, 会判断空间是否满足要求
      举例:
      // 字符串连接
      sds sdscat(sds s, const char *t) {
          return sdscatlen(s, t, strlen(t));
      }
      // 将长度为 len 的字符串 t 追加到 s 后面
      sds sdscatlen(sds s, const void *t, size_t len) {
          struct sdshdr *sh;
          // 原有字符串长度
          size_t curlen = sdslen(s);
          /*
          * 扩展 sds 空间
          * 根据 s 的 free 字段是否满足 len 长度, 决定是否扩展
          */
          s = sdsMakeRoomFor(s,len);
          // 扩展失败, 直接返回
          if (s == NULL) return NULL;
          // 复制 t 中的内容到字符串后部
          sh = (void*) (s-(sizeof(struct sdshdr)));
          memcpy(s+curlen, t, len);
          // 更新属性
          sh->len = curlen+len;
          sh->free = sh->free-len;
          // 添加新结尾符号
          s[curlen+len] = '\0';
          // 返回新 sds
          return s;
      }
      
      • sdsMakeRoomFor(s, len): 对 s 进行操作时候判断剩余空间是否满足, 决定是否扩展
      • sh = (void *) (s - (sizeof(struct sdshdr))): 对于字符串的操作, 根据 sds 地址计算出 sdshdr 的地址, 然后进行相应的属性赋值等操作
  • free 字段

    • 减少内存预分配带来的系统调用所造成的性能损耗

      c 语言对 string 结构并未进行任何的封装, 单纯的以 "\0" 作为结束符, 所以对字符串进行操作时, 比如增长操作时, 需要重新分配内存, 否则会造成缓冲区溢出; 缩减操作时, 需要重新分配内存, 否则会造成内存泄漏
      
    • 空间预分配 (字符串增长操作)

      /*
      * 为 sds 分配空间
      */
      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;
              
          newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
          // 内存不足,分配失败,返回
          if (newsh == NULL) return NULL;
          // 更新 sds 的空余长度
          newsh->free = newlen - len;
          // 返回 sds
          return newsh->buf;
      }
      
      • 分配内存的策略: 若新的字符串长度小于 SDS_MAX_PREALLOC (1024 * 1024), 则分配2倍的空间, 若大于, 则 += SDS_MAX_PREALLOC
    • 惰性空间释放 (字符串缩短操作)

      字符串在进行缩短操作时, 不会立即通过内存分配回收空闲空间, 而是将其更新到 free 字段, 以待将来增长操作时使用
      

sds api (src/sds.c)

函数 作用 备注
sdsnewlen 创建一个 sds, 使用指定的字符串和长度 sds sdsnewlen(const void *init, size_t initlen)
sdsempty 创建一个空 sds sds sdsempty(void)
sdsnew 创建一个 sds, 使用指定的字符串 sds sdsnew(const char *init)
sdsdup 复制给定的 sds sds sdsdup(const sds s)
sdsfree 释放给定的 sds void sdsfree(sds s)
sdsupdatelen 更新 sds 的长度字段 (free, len) void sdsupdatelen(sds s)
sdsclear 重置 sds 字符串对象为空 void sdsclear(sds s)
sdsMakeRoomFor sds 字符串增长 addlen长度 (判断空余空间) sds sdsMakeRoomFor(sds s, size_t addlen)
sdsRemoveFreeSpace 移除 sds 空闲空间 (free字段) sds sdsRemoveFreeSpace(sds s)
sdsAllocSize 返回给定 sds 对象存储所分配的空间 size_t sdsAllocSize(sds s)
sdsIncrLen sds 增长 incr 长度 (更新 len, free) void sdsIncrLen(sds s, int incr)
sdsgrowzero 将 sds 增长到 len 长度, 超出部分用 0 填充 sds sdsgrowzero(sds s, size_t len)
sdscatlen 将字符串 t 的 len 长度追加到 s 后面 sds sdscatlen(sds s, const void *t, size_t len)
sdscat 将字符串 t 追加到 s 后面 sds sdscat(sds s, const char *t)
sdscatsds 将 sds 结构的 t 追加到 s 后面 sds sdscatsds(sds s, const sds t)
sdscpylen 将 t 的 len 长度的字符串复制给 s, 作为新 s sds sdscpylen(sds s, const char *t, size_t len)
sdscpy 将字符串 t 复制给 s, 覆盖 s 原有字符串 sds sdscpy(sds s, const char *t)
sdsll2str 将 long long 类型转换为 string 类型 int sdsll2str(char *s, long long value)
sdsull2str 将 unsign long long 类型转换为 string 类型 int sdsull2str(char *s, unsigned long long v)
sdsfromlonglong 用 long long 类型创建一个 sds 类型字符串 sds sdsfromlonglong(long long value)
sdscatvprintf 将格式化后的字符串追加到 s 后 sds sdscatvprintf(sds s, const char *fmt, va_list ap)
sdscatprintf 将格式化后的字符串追加到 s 后 sds sdscatprintf(sds s, const char *fmt, ...)
sdscatfmt 将格式化后的字符串追加到 s 后 (redis自定义的格式标识符) sds sdscatfmt(sds s, char const *fmt, ...)
sdstrim 将 s 两端去掉指定字符集合中的字符 sds sdstrim(sds s, const char *cset)
sdsrange 截取 s 中的一段, 并赋值给 s void sdsrange(sds s, int start, int end)
sdstolower 将 s 转换为小写字母 void sdstolower(sds s)
sdstoupper 将 s 转换为大写字母 void sdstoupper(sds s)
sdscmp 比较 s1, s2 的大小 int sdscmp(const sds s1, const sds s2)
sdssplitlen 以指定分隔符 sep 将 s 分割成多个 token, 返回 sds 数组 sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count)
sdsfreesplitres 释放 sds 数组的 count 个 sds 内存空间 void sdsfreesplitres(sds *tokens, int count)
sdscatrepr 将 p 字符串的 len 长度中的转义字符跳脱转义显示出来, 追加到 s 后面 sds sdscatrepr(sds s, const char *p, size_t len)
hex_digit_to_int 将16进制字符转换为10进制的数值 int hex_digit_to_int(char c)
sdssplitargs 将给定字符串转义字符进行转义返回, 并按照 space 作为分隔符进行分隔, 分隔后的参数列表保存在 argc sds *sdssplitargs(const char *line, int *argc)
sdsmapchars 将 from 中的字符与 to 中的 setlen 字符一一对应, 对 s 进行替换 sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen)
sdsjoin 将 sds 数组中的 argc 个 sds 按照 sep 分隔符连接 sds sdsjoin(char **argv, int argc, char *sep)
posted on 2017-04-01 11:42  大粨兔奶糖  阅读(199)  评论(0编辑  收藏  举报