声明:这是本人参考黄建宏的《redis设计与实现》(源码版本是redis3.0)来学习redis3.20源码的笔记,如果有什么不对的地方,欢迎大家指正,大家一起学习、一起进步,QQ:499656254。

一、SDS介绍

SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:

1、O(1)时间内获取字符串长度。(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)

2、SDS提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)

3、减少了修改字符串时带来的内存重分配次数。

对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1

对于减少的字符串其并不立即释放空间,而是回归到alloc中去。

这个构建的结构在Redis3.20中的表示如下(和Redis2.x中还是有一定区别的):

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};

从代码中可以看出,SDS表示的字符串是有SDSheader和char*指针组成,而SDS的头部主要由四部分组成:

len:SDS字符串已使用的空间。

alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。

flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一                           部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。

buf:  用了C的特性表示不定长字符串。

二、API学习

1、sdsnewlen函数

函数原型:sds sdsnewlen(const void *init, size_t initlen)

说明:sdsnewlen用来创建init所指向对象作为内容的SDS,比如mystring = sdsnewlen("abc",3)。其中sdsnew函数也是调用sdsnewlen函数来实现的

返回值:buf数组的指针位置

sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen);//根据initlen的长度,选择不同的type,进一步来节省内存空间
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);//返回sdshdr结构体大小
unsigned char *fp; /* flags pointer. */ sh = s_malloc(hdrlen+initlen+1);//底层调用malloc申请空间
if (!init)
memset(sh, 0, hdrlen+initlen+1);//若创建的sds对象为空,则空间赋值0
if (sh == NULL) return NULL;//分配失败返回NULL
s = (char*)sh+hdrlen;//指向buf数组
fp = ((unsigned char*)s)-1;//指向flag
switch(type) {//根据type不同对sdshdr结构体进行赋值,len和alloc设置为initlen
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)//将字符串拷贝至分配的内存空间
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}

2、sdsMakeRoomFor函数

函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)

说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。

返回值:扩充后的sds对象

sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s);//返回剩余可用空间,即s->alloc - s->len
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen; /* Return ASAP if there is enough space left. */
if (avail >= addlen) return s; len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC; type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type);
if (oldtype==type) { //若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配。
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newlen);
return s;
}

  3、sdstrim函数

函数原型:sds sdstrim(sds s, const char *cset)

说明:从左右两边剔除sds对象包含集合CSET中的元素,内部通过memmove函数移位实现。

sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
size_t len; sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (s != sp) memmove(s, sp, len);
s[len] = '\0';
sdssetlen(s,len);
return s;
}

三、结论

sds数据类型是redis里面常用的数据类型,所以其在设计优化上面有了一定的改动(相对于redis2.x版本),比如其数据结构发生了改变。最后想说下,其源码实现确实比较简单,但是代码写的很nice(至少目前的我还写不出来,不过我要加油)

Redis3.20阅读-SDS实现的更多相关文章

  1. Redis源码阅读-sds字符串源码阅读

    redis使用sds代替char *字符串, 其定义如下: typedef char *sds; struct sdshdr { unsigned int len; unsigned int free ...

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

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

  3. Reids(4)——神奇的HyperLoglog解决统计问题

    一.HyperLogLog 简介 HyperLogLog 是最早由 Flajolet 及其同事在 2007 年提出的一种 估算基数的近似最优算法.但跟原版论文不同的是,好像很多书包括 Redis 作者 ...

  4. 给JavaScript初学者的24条最佳实践

    ­.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0 } .fluid-width-video-wrapp ...

  5. 编写高效的js/jQuery代码 :rocket:

    讨论jQuery和javascript性能的文章并不罕见.然而,本文我计划总结一些速度方面的技巧和我本人的一些建议,来提升你的jQuery和javascript代码.好的代码会带来速度的提升.快速渲染 ...

  6. 给JavaScript初学者的24条最佳实践(share)

    不错的文章,留个备份 原文链接: net.tutsplus   翻译: 伯乐在线- yanhaijing译文链接: http://blog.jobbole.com/53199/ 作为“30 HTML和 ...

  7. SQL数据类型解释

    SQL数据类型解释 1.char.varchar.text.ntext.bigint.int.smallint.tinyint和bit的区别及数据库的数据类型电脑秘籍 2009-05-15 21:47 ...

  8. 随笔分类 - 无废话ExtJs系列教程

    随笔分类 - 无废话ExtJs系列教程 摘自:http://www.cnblogs.com/iamlilinfeng/category/385121.html ExtJs 入门教程 摘要: extjs ...

  9. jQuery EasyUI API 中文文档

    http://www.cnblogs.com/Philoo/tag/jQuery/ 共2页: 1 2 下一页  jQuery EasyUI API 中文文档 - 树表格(TreeGrid) 风流涕淌 ...

随机推荐

  1. mysql slave to master

    1, 在maste A上面创建专门用于备份的用户Bshow master statusget log_file and log_pos 2,CHANGE MASTER TO MASTER_HOST=' ...

  2. No Spring WebApplicationInitializer types detected on classpath。启动时不报错,但是页面打不开。

    一片红,没有黑色disPatcher的加载. 百度,但是没有用,二十分钟浪费,这个问题的本质就是web.xml中的disPatcher没有加载,但是我肯定和代码无关,配置文件也没有变化过,值可能是to ...

  3. debian bcm43* 无线网卡驱动

    deb http://httpredir.debian.org/debian/ jessie main contrib non-free # apt-get update# apt-get insta ...

  4. Poj 1276 Cash Machine 多重背包

    Cash Machine Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 26172   Accepted: 9238 Des ...

  5. 【原创】我所理解的自动更新-外网web服务器配置

    ClientDownload和ClientUpdate共享渠道配置信息: channel-0.php //以appstore的渠道为例 <?php define('APPNAME', 'TOKE ...

  6. mybatis+MySQL--CRUD

    ①导入jar包: ②.配置config.xml: ③.entity: mapping: ④.DAO:   —————————————————————————————————— 目录结构: —————— ...

  7. python之haproxy配置文件操作(第三天)

    作业: 对haproxy配置文件进行操作 要求: 对haproxy配置文件中backend下的server实现增删改查的功能 一.这个程序有二个版本 1. python2.7版本见haproxy_py ...

  8. nfc相关

    nfc普通读卡写卡按厂商API操作即可,但是牵扯到NDEF的读写就另当别论了,算是二次开放了,android手机有成熟的接口,.net也有一些,github上有一个,还没研究, https://git ...

  9. MFC 修改各种控件的背景颜色、字颜色和字体

    今天主要总结一下有关MFC 中静态编辑框(StaticEdit).编辑框(Edit)和按钮(Button)的背景颜色.字颜色和字体. 我的程序运行结果如下: 由上图我们知道修改的地方有:1.把Stat ...

  10. wex5 实战 框架拓展之1 公共data组件(Data)

    一 前言 wex5作为开发利器,框架本身的集成能力与拓展能力可谓简单强大.在学习过程中,对框架的拓展能力,需要通过实践来丰富.今天,我以实际工作中的实例,先来看一看,框架上的公共data组件的实现与用 ...