redis的内部数据结构主要有:字符串双端链表字典跳跃表
这里主要记录redise字符串的设计。相关的源码位于:src/sds.h 和 src/sds.c。
 
一 字符串 sds的结构体
  1. struct sdshdr {
  2. int len; // buf 已占用长度
  3. int free; // buf 剩余可用长度
  4. char buf[]; // 实际保存字符串数据的地方
  5. };
从这个结构可以看出,redis字符串和C的不一样,本质字符串是保存在内存的某一个位置,然后把它的指针放到buf上。.
这种方式对于读取字符串的长度的很快,是O(1)。
另一个原因是redis 对字符串的追加操作比较频繁。这种方式的追加可以减少对内存的申请频度。
对于这种可以举个简单的例子:
  1. struct sdshdr {
  2. len = ;
  3. free = ;
  4. buf = "hello world\0"; // buf 的实际长度为 len + 1
  5. };
二 字符串的追加
当在buf后追加字符串时,发现free=0或不足于让新追加的字符串加到buf时,就会按照策略去申请更大的空间。如果free的大小足够大,就不会去申请。
申请的策略在sdsMakeRoomFor中。如下是redis的源码。
  1. /* Enlarge the free space at the end of the sds string so that the caller
  2. * is sure that after calling this function can overwrite up to addlen
  3. * bytes after the end of the string, plus one more byte for nul term.
  4. *
  5. * Note: this does not change the *length* of the sds string as returned
  6. * by sdslen(), but only the free buffer space we have. */
  7. sds sdsMakeRoomFor(sds s, size_t addlen) {
  8. struct sdshdr *sh, *newsh;
  9. size_t free = sdsavail(s);
  10. size_t len, newlen;
  11.  
  12. if (free >= addlen) return s;
  13. len = sdslen(s);
  14. sh = (void*) (s-(sizeof(struct sdshdr)));
  15. newlen = (len+addlen);
  16. if (newlen < SDS_MAX_PREALLOC)
  17. newlen *= ;
  18. else
  19. newlen += SDS_MAX_PREALLOC;
  20. newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+);
  21. if (newsh == NULL) return NULL;
  22.  
  23. newsh->free = newlen - len;
  24. return newsh->buf;
  25. }

其中,#define SDS_MAX_PREALLOC (1024*1024) 。如果新字符串的总长度小于 SDS_MAX_PREALLOC。 那么为字符串分配 2 倍于所需长度的空间。否则就分配所需长度加上 SDS_MAX_PREALLOC 数量的空间。

 
三 字符串的API
对于这样一个结构体,就应该有对应API提供给其他模块操作。sds对外API都在他的头文件中src/sds.h。
  1. /*
  2. 字符串的长度
  3. */
  4. static inline size_t sdslen(const sds s) {
  5. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
  6. return sh->len;
  7. }
  8.  
  9. /*
  10. 字符串剩余长度
  11. */
  12. static inline size_t sdsavail(const sds s) {
  13. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
  14. return sh->free;
  15. }
  16.  
  17. sds sdsnewlen(const void *init, size_t initlen); /* 创建新字符串,内部申请了内存 */
  18. sds sdsnew(const char *init); /* 对sdsnewlen的封装而已 */
  19. sds sdsempty(void); /*创建一个空的字符串 调用sdsnewlen */
  20. size_t sdslen(const sds s);
  21. sds sdsdup(const sds s); /* 拷贝 */
  22. void sdsfree(sds s); /* 释放 */
  23. size_t sdsavail(const sds s);
  24. sds sdsgrowzero(sds s, size_t len); /* 将给定 sds 的 buf 扩展至指定长度,无内容的部分用 \0 来填充 */
  25. sds sdscatlen(sds s, const void *t, size_t len); /* 追加一个C类型的字符串 带长度len */
  26. sds sdscat(sds s, const char *t); /* 调用sdscatlen, 在内部算长度 */
  27. sds sdscatsds(sds s, const sds t); /* 追加一个sds 字符串 */
  28. sds sdscpylen(sds s, const char *t, size_t len); /* 拷贝一个C类型的字符串 带长度len */
  29. sds sdscpy(sds s, const char *t); /* 调用sdscpylen, 在内部算长度 */
  30.  
  31. sds sdscatvprintf(sds s, const char *fmt, va_list ap);
  32. #ifdef __GNUC__
  33. sds sdscatprintf(sds s, const char *fmt, ...)
  34. __attribute__((format(printf, , )));
  35. #else
  36. sds sdscatprintf(sds s, const char *fmt, ...);
  37. #endif
  38.  
  39. sds sdscatfmt(sds s, char const *fmt, ...);
  40. sds sdstrim(sds s, const char *cset);
  41. void sdsrange(sds s, int start, int end); /* 取出子串 end为负时从后面往前算起 */
  42. void sdsupdatelen(sds s); /* 当手动强制把字符串砍掉时, 要用sdsupd telen更新len和free */
  43. void sdsclear(sds s); /* 清除掉当前的字符串 */
  44. int sdscmp(const sds s1, const sds s2); /* 比较两个字符串 */
  45. /* 把s按sep分割, len是s的长度,seplen是sep的长度 */
  46. sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
  47. void sdsfreesplitres(sds *tokens, int count); /* 释放 sdssplitlen 的返回值,sdssplitlen专用啊,其实就是释放一个数组 */
  48. void sdstolower(sds s); /* 转为小写 */
  49. void sdstoupper(sds s); /* 转为大写 */
  50. sds sdsfromlonglong(long long value); /*long long 转为字符串 */
  51. sds sdscatrepr(sds s, const char *p, size_t len);
  52. sds *sdssplitargs(const char *line, int *argc);
  53. sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
  54. sds sdsjoin(char **argv, int argc, char *sep);
  55.  
  56. /* Low level functions exposed to the user API */
  57. sds sdsMakeRoomFor(sds s, size_t addlen); /* 按策略申请长度 */
  58. void sdsIncrLen(sds s, int incr);
  59. sds sdsRemoveFreeSpace(sds s);
  60. size_t sdsAllocSize(sds s);

大致看了一些实现,还算是比较清晰。可以了解几个比较主要的函数。其中sdsnewlen是对字符串的初始化。

  1. /* Create a new sds string with the content specified by the 'init' pointer
  2. * and 'initlen'.
  3. * If NULL is used for 'init' the string is initialized with zero bytes.
  4. *
  5. * The string is always null-termined (all the sds strings are, always) so
  6. * even if you create an sds string with:
  7. *
  8. * mystring = sdsnewlen("abc",3");
  9. *
  10. * You can print the string with printf() as there is an implicit \0 at the
  11. * end of the string. However the string is binary safe and can contain
  12. * \0 characters in the middle, as the length is stored in the sds header. */
  13. sds sdsnewlen(const void *init, size_t initlen) {
  14. struct sdshdr *sh;
  15. if (init) {
  16. sh = zmalloc(sizeof(struct sdshdr)+initlen+);
  17. } else {
  18. sh = zcalloc(sizeof(struct sdshdr)+initlen+);
  19. }
  20. if (sh == NULL) return NULL;
  21. sh->len = initlen;
  22. sh->free = ;
  23. if (initlen && init)
  24. memcpy(sh->buf, init, initlen);
  25. sh->buf[initlen] = '\0';
  26. return (char*)sh->buf;
  27. }
  28. /* Free an sds string. No operation is performed if 's' is NULL. */
  29. void sdsfree(sds s) {
  30. if (s == NULL) return;
  31. zfree(s-sizeof(struct sdshdr));
  32. }
其中zmalloc zcalloc 和zfree 是申请内存的。封装malloc和calloc,主要是考虑到跨平台的情况。不过从这个地方可以看出redis在对内存申请与释放到什么独到的管理方式。这种方式用sds字符串,一不小心就可能会内存泄漏了。
 
四 好处与坏外
按照《redis 设计与实现》这书的说法,是:
对比 C 字符串,sds 有以下特性:
 可以高效地执行长度计算( strlen);
 可以高效地执行追加操作( append);
 二进制安全;
 sds 会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,代价是多占
用了一些内存,而且这些内存不会被主动释放。
 
五 抽出模块
看redis字符串模块的源码的过程中,抽出简化一些,做了一个test。放在了github上。地址是:https://github.com/CarlosFang/modrds/tree/master/string
 
 
 

redis 源码阅读 内部数据结构--字符串的更多相关文章

  1. [Redis源码阅读]sds字符串实现

    初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...

  2. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  3. Redis源码阅读(三)集群-连接初始化

    Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...

  4. Redis源码阅读-Adlist双向链表

    Redis源码阅读-链表部分- 链表数据结构在Adlist.h   Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...

  5. Redis源码阅读(六)集群-故障迁移(下)

    Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...

  6. Redis源码阅读(四)集群-请求分配

    Redis源码阅读(四)集群-请求分配 集群搭建好之后,用户发送的命令请求可以被分配到不同的节点去处理.那Redis对命令请求分配的依据是什么?如果节点数量有变动,命令又是如何重新分配的,重分配的过程 ...

  7. Redis源码阅读(一)事件机制

    Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...

  8. Redis源码阅读(五)集群-故障迁移(上)

    Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...

  9. Redis源码阅读一:简单动态字符串SDS

    源码阅读基于Redis4.0.9 SDS介绍 redis 127.0.0.1:6379> SET dbname redis OK redis 127.0.0.1:6379> GET dbn ...

随机推荐

  1. byte为什么要与上0xff?

    无意间翻看之间的代码,发现了一段难以理解的代码. byte[] bs = digest.digest(origin.getBytes(Charset.forName(charsetName))) ; ...

  2. PHP5各个版本的新功能和新特性总结

    因为 PHP 那“集百家之长”的蛋疼语法,加上社区氛围不好,很多人对新版本,新特征并无兴趣.本文将会介绍自 PHP5.2 起,直至 PHP5.6 中增加的新特征 本文目录:PHP5.2 以前:auto ...

  3. 学习笔记(二)——MVC扩展(渲染视图)

    如何渲染视图? 我以近乎的视图引擎为例总结了一下,近乎中的ThemedViewEngine类,就是重写后的的视图引擎.ThemedViewEngine类主要对FindPartialView和FindV ...

  4. 【C#进阶系列】29 混合线程同步构造

    上一章讲了基元线程同步构造,而其它的线程同步构造都是基于这些基元线程同步构造的,并且一般都合并了用户模式和内核模式构造,我们称之为混合线程同步构造. 在没有线程竞争时,混合线程提供了基于用户模式构造所 ...

  5. 连接输出 如果存在在php中多次echo输出js的时候

  6. Java时间和时间戳的相互转换

    时间转换为时间戳: /* * 将时间转换为时间戳 */ public static String dateToStamp(String s) throws ParseException{ String ...

  7. Spring(二)__bean的装配

    Bean的装配: 在spring容器内拼凑bean叫做装配.装 配bean的时候,需要告诉容器哪些bean 以及容器如何使用依赖注入将它们配合在一起. 上下文定义文件的根元素是<beans> ...

  8. Mac下如何查看Tomcat的版本?

    Tomcat提供了一个查询自身版本号的方法,要查询Tomcat的版本号,必须知道Tomcat所在的准确目录. 例如: 所用的Tomcat所在的目录下的bin文件夹的完整路径为:/Library/Tom ...

  9. 弄一个ajax笔记方便查询-基础知识篇

    jQuery对Ajax做了大量的封装,jQuery采用了三层封装: 最底层的封装方法为:$.ajax() 通过最底层进一步封装了第二层的三种方法:.load().$.get().$.post() 最高 ...

  10. 【高级功能】使用 Ajax(续)

    1. 准备向服务器发送数据 Ajax 最常见的一大用途是向服务器发送数据.最典型的情况是从 客户端发送表单数据,即用户在form元素所含的各个 input 元素里输入的值.下面代码展示了一张简单的表单 ...