2019独角兽企业重金招聘Python工程师标准>>>

本文为作者原创,转载请注明出处:http://my.oschina.net/fuckphp/blog/269167

在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对象的时候可以非常方便的获取。

  1. 创建字符串 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);

2.   根据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宁可每次手动写,也没有对此进行一个宏定义的封装)。

3.  计算字符串长度

计算长度的方式就非常简单了只需要根据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;
}

4.  追加字符串

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;elsenewlen += 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() 对多申请的那部分内存进行释放。

更多字符串函数可以参考我的另外一篇文章:

Redis 2.8.9字符串操作函数头整理,并注释作用和参数说明

参考资料:

Redis2.8.9源码   src/sds.h   src/sds.c

Redis 设计与实现(第一版)

非常感谢 Redis 设计与实现的作者,给我看代码带来了很大的方便。



转载于:https://my.oschina.net/fuckphp/blog/269167

Redis 2.8.9源码 - Redis中的字符串实现 sds相关推荐

  1. redis 4.0.8 源码包安装集群

    系统:centos 6.9 软件版本:redis-4.0.8,rubygems-2.7.7,gcc version 4.4.7 20120313,openssl-1.1.0h,zlib-1.2.11 ...

  2. 【作者面对面问答】包邮送《Redis 5设计与源码分析》5本

    墨墨导读:本文节选自<Redis 5设计与源码分析>,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行 ...

  3. 选redis还是memcache,源码怎么说?

    选redis还是memcache,源码怎么说? memcache和redis是互联网分层架构中,最常用的KV缓存.不少同学在选型的时候会纠结,到底是选择memcache还是redis. 画外音:不鼓励 ...

  4. 霸榜巨作、阿里内部顶级专家整理(Redis 5设计与源码分析)

    前言 在开源界,高性能服务的典型代表就是Nginx和Redis.纵观这两个软件的源码,都是非常简洁高效的,也都是基于异步网络I/O机制的,所以对于要学习高性能服务的程序员或者爱好者来说,研究这两个网络 ...

  5. Redis集群模式源码分析

    目录 1 主从复制模式 2 Sentinel(哨兵)模式 3 Cluster模式 4.参考文档 1 主从复制模式 主库负责读写操作,从库负责数据同步,接受来自主库的同步命令.通过分析Redis的客户端 ...

  6. 新书推荐 |《Redis 5设计与源码分析》

    新书推荐 <Redis 5设计与源码分析> 点击上图了解及购买 好未来.滴滴.百度等公司专家联合撰写,掌握Redis 5设计与命令实现,透彻掌握分布式缓存. 编辑推荐 多名专家联袂推荐,资 ...

  7. php5.5n的redis,TP5源码 Redis操作便捷类库

    # TP5源码 Redis操作便捷类库 > app\base\controller\Redis ~~~ /** * Created by PhpStorm. * Power by Mikkle ...

  8. Redis分布式锁解析源码分析

    Redis分布式锁解析&源码分析 概述 实战 简单的分布式锁 Redisson实现分布式锁 Redission源码分析 构造方法 获取锁lock 解锁 锁失效 红锁 案例分析 原始的写法 进化 ...

  9. 游戏陪玩app源码开发中,摄像头的调用及视频处理

    摄像头是游戏陪玩app源码进行视频连麦时的重要移动设备之一,在开发时,我们需要实现游戏陪玩app源码对摄像头的调用权限,这就涉及到相关接口的开发了,不过今天我们主要来了解一下在游戏陪玩app源码开发中 ...

最新文章

  1. java 密钥工厂 desede_20145212 实验五《Java网络编程》
  2. @AutoWired和@Resource注解异同分析
  3. The J2EE Architect's Handbook
  4. ashx和asmx的HttpContext
  5. java数据库编程——执行查询操作(二)
  6. 晒2012年度十大杰出IT博客 奖品
  7. window下git的用户切换_Windows下Git的使用
  8. 重大安全事件 | Ubuntu 16.04.4 暴本地提权漏洞
  9. Javascript学习之创建对象
  10. 电脑初学者怎么学习写代码?代码怎样写入电脑?
  11. 映美530k驱动不能安装问题
  12. Ubuntu常见错误合集——持续更新
  13. Python解标准数独
  14. mysql 多表联合查询
  15. An Adaptive Stochastic Dominant Learning Swarm Optimizer for High-Dimensional Optimization 阅读笔记
  16. html设置label的字体大小,swift - label 的font 设置 文字字体和大小
  17. #国产工业软件#外行人看工业软件,接轨还是出轨?
  18. android 2.3 刷机,【MIUI DFY】【Android 2.3.4】 刷机教程.doc
  19. CentOS 7 服务器密码忘记的解决办法
  20. Latex报错 ! Misplaced alignment tab character

热门文章

  1. 游戏服务器系统是什么意思,游戏服务器都是什么系统
  2. python求最大公因数函数_面试试讲 | 17学员音频示范《最大公约数》+教案+试讲稿...
  3. 我的世界服务器怎么注册密码1.9,我的世界1.9.x /1.10服务器BEST CRAFT(最佳工艺服务器)...
  4. java中sort的cmp_快速排序(cmp函数详解)
  5. android ddmlib,通过 ddmlib 使用 adb,构建框架基础库
  6. axure插件怎么用_CAD插件不会用怎么行?CAD插件大全合集,超实用绘图软件,高效...
  7. golang redis获取所有key_Redis 内存分析神器
  8. sqlalchemy 聚合
  9. Linux进程间通信的方法和示例
  10. java类无法调用值,Kotlin无法调用到Java中定义的interface类的问题记录