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)

redis 字符串的更多相关文章

  1. redis字符串

    字符串类型是redis的基本类型 字符串数据类型的相关命令用于管理 redis 字符串值,基本语法如下:COMMAND KEY_NAME SET 和GET用于设置和读取key的值 1.SET key ...

  2. Redis 字符串(String)

      Redis 字符串数据类型的相关命令用于管理 redis 字符串值,基本语法如下: 语法 redis 127.0.0.1:6379> COMMAND KEY_NAME 实例 redis 12 ...

  3. 2016022608 - redis字符串命令集合

    redis字符串命令: Redis字符串命令用于在Redis管理字符串值.使用Redis字符串命令的语法如下所示: redis 127.0.0.1:6379> COMMAND KEY_NAME ...

  4. redis 字符串的管理的一点理解

    redis字符串可以实现通过地址偏移找到所在结构体的首地址,struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr))) 也就是通过buf地址可以 ...

  5. Python操作redis字符串(String)详解 (三)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1.SET 命令用于设置 ...

  6. Redis 字符串(String)

    Redis 字符串(String) Redis 字符串数据类型的相关命令用于管理 redis 字符串值,基本语法如下: 语法 redis 127.0.0.1:6379> COMMAND KEY_ ...

  7. redis字符串基本操作

    redis之字符串类型: 字符串类型是redis中最基本的数据类型,同时它也是memcached中仅有的数据类型.redis字符串类型的键能存储任何形式的字符串,包括二进制数据,例如,存储json化的 ...

  8. Redis字符串(STRING)中BIT相关命令

    上篇文章我们对STRING数据类型中一些基本的命令进行了介绍,但是没有涉及到BIT相关的命令,本文我们就来看看几个和BIT相关的命令. 本文是Redis系列的第四篇文章,了解前面的文章有助于更好的理解 ...

  9. Redis字符串键的底层原理

    before C语言基础 Redis基础 导入 redis的命令如下: set x "hello"; get x; hello Redis作为一种存储字符串的缓存结构,其具体实现是 ...

随机推荐

  1. Mac OSX Sierra WiFi connecting problem

    吐槽一下,苹果的质量管控越来越差了. Mac OSX Sierra有时突然或升级后会遇到wifi不停重连连不上问题,现象为不停地连接wifi. 网上有人说删除 /Library/Preferences ...

  2. react学习总结

    http://www.runoob.com/react/react-tutorial.html一般先看一些中文的简单的介绍和一些基本概念http://reactjs.cn/react/docs/get ...

  3. 1634: [Usaco2007 Jan]Protecting the Flowers 护花

    1634: [Usaco2007 Jan]Protecting the Flowers 护花 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 493  So ...

  4. scanf函数之扫描集

    前言 %[]是scanf函数不常用到的格式字符--扫描集(scanset),它的用法很简单,但却能巧妙地解决一些输入问题.(由于书籍里和网上对此格式字符的介绍很少且篇幅较小,本文可能会有些许错误,请读 ...

  5. 百度推送-sitemap-使用playframework框架实现-java

    主动推送的目的是能够把我们高质量内容推送给百度,但是首先你得有一个属于你自己的网站,在百度站长进行验证通过之后,才有资格推送百度sitemap. 百度站长平台为未使用百度统计的站点提供三种验证方式:文 ...

  6. git中常见的几个命令

    git中常见的几个命令 本地仓库 三个区域 工作目录 暂存区 本地仓库 文件的四个状态 未跟踪 untracked 已暂存 staged 已提交commited 已修改 modified 基本命令 g ...

  7. 享受release版本发布的好处的同时也应该警惕release可能给你引入一些莫名其妙的大bug

    一般我们发布项目的时候通常都会采用release版本,因为release会在jit层面对我们的il代码进行了优化,比如在迭代和内存操作的性能提升方面,废话不多说, 我先用一个简单的“冒泡排序”体验下r ...

  8. js应用之实现图片切换效果

    数组的操作与应用 数组的定义 var 数组名=new Array(); //创建空数组 var 数组名=new Array(size);//创建指定数组长度的数组 var 数组名=new Array( ...

  9. 前端资讯周报 3.13 - 3.19: WebVR来了!以及如何优化scroll事件性能

    每周一我都会分享上一周我订阅的技术站点中,和解决问题的过程中阅读到的值得分享的文章.这是迫使我学习的一个动力 本周推荐 Minecraft in WebVR with HTML Using A-Fra ...

  10. RequireJS基础知识

    RequireJS解决代码依赖问题,异步加载js,避免页面失去相应 RequireJS的目标是鼓励代码的模块化,它使用了不同于传统<script>标签的脚本加载步骤.可以用它来加速.优化代 ...