redis支持多种数据类型,sds(simple dynamic string)是最基本的一种,redis中的字符串类型大多使用sds保存,它支持动态的扩展与压缩,并提供许多工具函数。这篇文章将分析sds在redis中是如何实现的。

1.    sds类型

sds在redis中其实就是一个char*类型的别名,声明如下:

typedef char *sds;

但是,以sds指向的字符串的存储格式具有一定的规则,即在字符串数据之前存储了相应的头部信息,这些头部信息包含了:1. alloc-分配的内存空间长度。2. len-有效字符串长度。3. flags-头部类型。

redis中有sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64这5类头部类型,其声明如下:

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[];
};

其它几种类型类似,但len与alloc字段根据头部类型使用不同的类型,sdshdr16使用uint16_t,sdshdr32使用uint32_t,sdshdr64使用uint64_t。另外,sdshdr5与其它头部类型不同,没有len与alloc字段,并将字符串实际长度保存在flags字段的高5bits。

sdshdr8由于alloc为uint8_t类型,因此可以表示的字符串最长为255字节;sdshdr16由于alloc为uint16_t类型,因此可以表示的字符串最长为65536字节;类似的,redis中选择最合适的头部去存储字符串,节约少许空间(我认为当字符串较短时,使用sdshdr8可以节约空间,但当字符串长度超过了uint8_t的表示范围,使用sdshdr16与sdshdr32,头部长度占所用空间的比例差别不大)。选择该使用何种头部的函数实现如下:

static inline char sdsReqType(size_t string_size) {
if (string_size < <<)
return SDS_TYPE_5;
if (string_size < <<)
return SDS_TYPE_8;
if (string_size < <<)
return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
if (string_size < 1ll<<)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else
return SDS_TYPE_32;
#endif
}

2.    sds操作

2.1  sds类型与实际分配内存之间的转换

sds的类型指向有效的字符串起始位置,头部信息与有效字符串的存储空间是统一分配的,它们的内存空间的连续的,因此将sds向前移动头部长度,即可得到实际分配的内存起始地址。而sds头部长度由头部类型决定,其实现如下:

static inline int sdsHdrSize(char type) {
switch(type&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return sizeof(struct sdshdr5);
case SDS_TYPE_8:
return sizeof(struct sdshdr8);
case SDS_TYPE_16:
return sizeof(struct sdshdr16);
case SDS_TYPE_32:
return sizeof(struct sdshdr32);
case SDS_TYPE_64:
return sizeof(struct sdshdr64);
}
return ;
}

sds的头部类型保存在头部的flags的低3bits,头部类型可以通过如下方式获得

oldtype = s[-] & SDS_TYPE_MASK; //s为sds类型

实际分配内存地址由如下方式获得

sh = (char*)s-sdsHdrSize(oldtype);

所此,sdsfree操作实现如下:

void sdsfree(sds s) {
if (s == NULL) return;
s_free((char*)s-sdsHdrSize(s[-]));
}

2.2  为c_str分配sds类型

新建sds类型的函数声明为:

sds sdsnewlen(const void *init, size_t initlen)

它的大致步骤可描述如下:

  1. 根据c_str的长度选择合适的sds头部类型,这一步由sdsReqType()函数实现
  2. 分配足够的空间存储头部与有效字符串(sds字符串末尾需要一个字节存储’\0’),这一步由s_malloc()函数实现。
  3. 设置头部信息,将c_str内容copy到sds中。

函数具体实现如下:

sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen);
/* 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 == ) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */ sh = s_malloc(hdrlen+initlen+);
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, , hdrlen+initlen+);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-;
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}

2.3  为sds增加存储空间

为已存在sds类型增加存储空间的操作函数声明为:

sds sdsMakeRoomFor(sds s, size_t addlen)

它的操作步骤大致如下:

  1. 查看sds中是否有足够的剩余空间容纳addlen长度的字符串,有则返回,无则继续其它操作。
  2. 计算需要重新分配的存储空间的长度,包括原sds长度与addlen,另外预备一部分的剩余空间。
  3. 根据新的长度,得到新的sds头部类型,如果新的头部类型与原类型相同,则使用s_realloc分配更多的空间;如果新的头部类型与原类型不相同,则使用s_alloc重新分配内存,并将原sds内容copy到新分配的空间。

函数具体实现如下:

sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-] & 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 *= ;
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) {
newsh = s_realloc(sh, hdrlen+newlen+);
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+);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newlen);
return s;
}

2.4  释放sds未使用的存储空间

sds头部alloc记录了分配的内存空间,len记录了实际使用的内存空间,当需要释放未使用的内存空间时,根据len记录,可以使用s_realloc压缩空间或者使用s_alloc重新分配更小的空间,释放旧的内存。相应函数声明为:

sds sdsRemoveFreeSpace(sds s) 

大致操作步骤如下:

  1. 根据len计算新的sds类型与所需内存空间。
  2. 如果新的类型与原sds类型相同,使用s_realloc压缩原内存空间。
  3. 如果新的类型与原sds类型不相同, 使用s_alloc重新分配空间,将原内容copy到新分配的sds中,并释放原内存。

sdsRemoveFreeSpace的实现与sds sdsMakeRoomFor大致相同,此处不再列出。

另外sds还提供了很多工具函数,如cat操作,copy操作,ll2str(整数转字符串),vprintf操作(格式化操作)等。具体实现见源码文件sds.c。

redis源码分析(一)-sds实现的更多相关文章

  1. Redis源码分析(sds)

    源码版本:redis-4.0.1 源码位置:https://github.com/antirez/sds 一.SDS简介 sds (Simple Dynamic String),Simple的意思是简 ...

  2. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  3. redis源码分析之事务Transaction(下)

    接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...

  4. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  5. Redis源码分析(intset)

    源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...

  6. Redis源码分析(dict)

    源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...

  7. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

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

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

  9. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

随机推荐

  1. [RN] React Navigation 使用中遇到的显示 问题 汇总

    React Navigation 使用中遇到的显示 问题 汇总 https://www.jianshu.com/p/8b1f18affc5d

  2. 你知道多少this,new,bind,call,apply?那我告诉你

    那么什么是this,new,bind,call,apply呢?这些你都用过吗?掌握这些内容都是基础中的基础了.如果你不了解,那还不赶快去复习复习,上网查阅资料啥的! 通过call,apply,bind ...

  3. 拼图验证码 js,vue

    可查看github网站

  4. JMeter的基本使用

    什么是Jmeter JMeter是Apache基于Java开发的压力测试工具,通俗的说,你想知道你的接口有多猛,你的服务器是否耐揍,这个家伙可以用数据告诉你.原来学过JMeter的基本使用,发现想不起 ...

  5. eclipseWeb项目如何实现网址发送给外人——内部穿透

    教程:https://blog.csdn.net/Feihongxiansen/article/details/94480480 部署完成后: 打开cmd命令: 暴露端口8081成功: 将eclips ...

  6. 2018-2019-2 20175217 实验四《Android开发基础》实验报告

    一.实验报告封面 课程:Java程序设计 班级:1752班 姓名:吴一凡 学号:20175217 指导教师:娄嘉鹏 实验日期:2019年5月16日 实验时间:--- 实验序号:实验四 实验名称:And ...

  7. ubuntu之路——day19.1 深度CNN的探究

    1.经典的CNN LeNet-5 1998的CNN鼻祖 以前用的sigmoid和tanh 下图给的是relu和softmax AlexNet ImageNet2012的冠军 VGG-16 ImageN ...

  8. Python适合练手的项目

    原文地址:https://www.jianshu.com/p/039156321e30 项目地址:https://github.com/DeqianBai/Python-Project/tree/ma ...

  9. cesium地形瓦片(Quantized-mesh)格式

    目录 1.切片规则 2.瓦片格式分析 2.1.数据头部 2.顶点数据 2.3.索引数据 2.4.扩展数据 参考资料: quantized-mesh-1.0 terrain format(用于三维可视化 ...

  10. 考勤打卡机导出的excel考勤时间表如何生成实用的考勤表

    该excel表有如下结构 姓名\日期 周1 周2 周3 周4 周5 张三 7:3518:02 7:3518:02 7:46   17:56 李四 7:3518:02 7:02 18:00 18:02 ...