【每日一博】Redis 中的字符串实现 sds

在C中子字符串的实现都是用 char *来实现的,用起来很不方便,而且容易出现内存泄露,并且效率不高,在Redis内部,字符串采用了 sds 的方式进行了封装,似的字符串在Redis中可以方便、高效的使用,Redis字符串的实现如要依赖一下两个数据类型和结构(以下代码可以在 src/sds.h中找到):

typedef char *sds;
sds 存放了字符串的具体值

struct sdshdr {
    int len;   //字符串对象已经使用的内存数量
    int free;  //字符串对象剩余的内存数量
    char buf[]; //字符串对象的具体值(其实就是sds字符串)
};

sdshdr 实现了字符串对象

这样设计的好处有很多,比如使得Redis在获取字符串长度的时候可以达到o(1)的复杂度,在进行追加等字符串操作的时候,可以减少内存分配(提高性能),sdshdr的结构使得根据sds字符串获取对应的sds对象的时候可以非常方便的获取。

创建字符串 init 为需要初始化的字符串值。initlen表示为初始化字符串的长度,该函数创建一个sds字符串对象并返回sds字符串(以下代码可以在 src/sds.c中找到):

sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh; //创建一个空的字符串对象

    //如果init为空的时候需要对分配的内存进行初始化
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    //设置字符串对象的已占用长度
    sh->len = initlen;
    sh->free = 0;
    //如果init不为空将其复制到字符串对象中
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    //在结尾加入终止符(c语言字符串以\0为结尾)
    sh->buf[initlen] = '\0';
    //返回字符串对象中的sds值
    return (char*)sh->buf;
}

例如你创建了一个sds字符串 为 hello 那么你的代码应该如下:

sds str = sdsnewlen("hello", 5);

这时候,Reids会创建一个sdshdr对象,长度为:

sizeof(struct sdshdr) + 5 + 1

Redis在释放字符串也会分方便,因为是对整个结构进行的分配所以只需要对sds字符串的对象进行释放就可以将字符串值和字符串对象的内存都释放掉,如下:

void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}

释放上例中的sds字符串只需要简单的调用:

sdsfree(str);
  1. 根据sds字符串获取对应的字符串对象

如1中你知道了如何创建一个字符串对象并返回它的sds字符串,根据sdshdr的存储结构,你可以方便的通过返回的sds字符串得到字符串对象,如下代码:(下面代码中s表示sds字符串,定义为 sds s;)

struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

由1中创建的hello的字符串对象在内存中的分配大概如下图:

上例中的 s 为 sdshdr结构中的buf元素,上例代码中的 s - sizeof(struct sdshdr) 会将指向buff 的指针,移动到len上,这样通过一个简单的运算就可以获取到sds字符串的对象,并对其进行字符串操作(不知道为啥redis宁可每次手动写,也没有对此进行一个宏定义的封装)。

  1. 计算字符串长度

计算长度的方式就非常简单了只需要根据sds字符串获取到sds对象,然后获取其len属性即可,具有o(1)的效率,而不需要去遍历字符列表,如下获取方法(以下代码可以在 src/sds.h中找到):

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}
  1. 追加字符串

Redis的追加字符串由于其设计方式可以非常高效,进行追加,直接看代码(以下代码可以在 src/sds.c中找到):

sds sdscatlen(sds s, const void *t, size_t len) {
    struct sdshdr *sh;      //定义一个字符串对象
    size_t curlen = sdslen(s);   //获取当前sds字符串的长度 可以参考第3条

    s = sdsMakeRoomFor(s,len);   //对sds字符串扩展,申请len长度的内存(会根据free决定是否申请,见下文)
    if (s == NULL) return NULL;
    sh = (void*) (s-(sizeof(struct sdshdr)));   //根据新申请空间后的sds字符串获取对应的对象
    memcpy(s+curlen, t, len);    //将新的字符串追加到结尾
    sh->len = curlen+len;     //更新已占用空间
    sh->free = sh->free-len;  //更新剩余空间
    s[curlen+len] = '\0';     //设置字符串结尾
    return s;     //返回修改后的sds字符串
}

对sds字符串内存进行扩展的函数如下:(以下代码可以在 src/sds.c中找到):

sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;   //初始化两个字符串对象
    size_t free = sdsavail(s);   //字符串剩余内存,定义在src/sds.h中,获取方法与 sdslen()相同
    size_t len, newlen;

    if (free >= addlen) return s;
    len = sdslen(s);   //获取当前字符串长度
    sh = (void*) (s-(sizeof(struct sdshdr)));   //获取当前的字符串对象
    newlen = (len+addlen);    //计算扩展后的字符串长度
    //一下为重点:申请字符串会计算,新的长度是否会超过SDS_MAX_PREALLOC(定义在src/sds.h中,默认为1M)
    //如果超过则申请SDS_MAX_PREALLOC大小的内存,否则申请2*扩展后字符串的长度
    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;
}

如上代码所述,系统在扩展内存的时候,会申请新字符串长度的两倍,这样后续在进行追加操作的时候就不进行内存分配处理了,节省了很多内存分配的消耗,当然这样可能会对内存造成一些浪费,Redis的一些配置可以改变这种行为,可以通过字符串函数 sdsRemoveFreeSpace() 对多申请的那部分内存进行释放。

时间: 2024-10-11 04:38:38

【每日一博】Redis 中的字符串实现 sds的相关文章

Redis中的动态字符串学习教程_Redis

sds 的用途Sds 在 Redis 中的主要作用有以下两个: 实现字符串对象(StringObject): 在 Redis 程序内部用作 char* 类型的替代品: 以下两个小节分别对这两种用途进行介绍. 实现字符串对象 Redis 是一个键值对数据库(key-value DB), 数据库的值可以是字符串.集合.列表等多种类型的对象, 而数据库的键则总是字符串对象. 对于那些包含字符串值的字符串对象来说, 每个字符串对象都包含一个 sds 值. "包含字符串值的字符串对象",这种说法

简介Redis中的showlog功能

  这篇文章主要介绍了简介Redis中的showlog功能,作者同时对比了DEL命令的性能,需要的朋友可以参考下 Redis 有一个实用的slowlog功能,正如你可以猜到的,可以让你检查运行缓慢的查询. Slowlog 将会记录运行时间超过Y微秒的最后X条查询. X 和 Y 可以在 redis.conf 或者在运行时通过 CONFIG 命令: 代码如下: CONFIG SET slowlog-log-slower-than 5000 CONFIG SET slowlog-max-len 25

redis中使用redis-dump导出、导入、还原数据实例

redis的备份和还原,借助了第三方的工具,redis-dump 1.安装redis-dump 复制代码代码如下: [root@localhost tank]# yum install ruby rubygems ruby-devel   //安装rubygems 以及相关包   [root@localhost tank]# gem sources -a http://ruby.taobao.org/   //源,加入淘宝,外面的源不能访问   http://ruby.taobao.org/ a

Java中Json字符串直接转换为对象的方法(包括多层List集合)_java

使用到的类:net.sf.json.JSONObject  使用JSON时,除了要导入JSON网站上面下载的json-lib-2.2-jdk15.jar包之外,还必须有其它几个依赖包:commons-beanutils.jar,commons-httpclient.jar,commons-lang.jar,ezmorph.jar,morph-1.0.1.jar 下面是例子代码: // JSON转换 JSONObject jsonObj = JSONObject.fromObject(jsonSt

Office Open XML学习(1)-创建excel文档,并向单元格中插入字符串

做企业级应用,跟office打交道是少不了的.这里的Office不仅仅局限于微软的Office,还有第三方的Open Office之类..Net传统的Office操作方法(比如OleDB,OWC之类),有几大缺点: 一是不通用(仅能处理微软的Office,不能与其它非Windows平台交换数据),二是性能差(导出一个Excel,如果记录数上万条,速度很慢),三是服务器通常要安装Office Web Components(即OWC组件).   自从Open XML出现后,这种情况在很大程度上得到了

JS中判断字符串中出现次数最多的字符及出现的次数的简单实例_javascript技巧

JS中判断字符串中出现次数最多的字符及出现的次数的简单实例 <script type="text/javascript"> var str = 'qwertyuilo.,mnbvcsarrrrrrrrtyuiop;l,mhgfdqrtyuio;.cvxsrtyiuo'; var json = {}; //遍历str拆解其中的每一个字符将其某个字符的值及出现的个数拿出来作为json的kv for (var i = 0; i < str.length; i++) { //

Java中的字符串用法小结_java

本文实例总结了Java中的字符串用法.分享给大家供大家参考.具体分析如下: 字符串的本质是char类型的数组,但在java中,所有用双引号""声明的字符串都是一个String类的对象.这也正体现了Java完全面向对象的语言特点. String 类 1.String类对象表示的是一个常量字符串.它是不可变长度的.也就是说,一旦创建了一个String类的实例,那么这个实例所表示的串是不可改变的.类似于 str = str + "Hello"; 这样的操作,实质上是将 s

Java中分割字符串的两种方法实例详解_java

前言 相信大家应该都知道在java编程中,有时候我们需要把一个字符串按照某个特定字符.字母等作为截点分割这个字符串,这样我们就可以使用这个字符串的一部分或者把所有截取的内容保存到数组里等操作.下面这篇文章就给大家分享了两种分割的方法,下面来一起看看吧. 一.java.lang.String 的 split() 方法, JDK 1.4 or later public String[] split(String regex,int limit) 示例代码 public class StringSpl

解析C++中的字符串处理函数和指针_C 语言

C++字符串处理函数 字符串连接函数 strcat 其函数原型为 strcat(char[],const char[]); strcat是string catenate(字符串连接)的缩写.该函数有两个字符数组的参数,函数的作用是:将第二个字符数组中的字符串连接到前面字符数组的字符串的后面.第二个字符数组被指定为const,以保证该数组中的内容不会在函数调用期间修改.连接后的字符串放在第一个字符数组中,函数调用后得到的函数值,就是第一个字符数组的地址.例如: char str1[30]=″Peo