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. 【JZOJ6214】【20190614】tetris

    题目 这是一道和俄罗斯方块有关的有趣题目 底面宽度为\(N\),高度无限,初始时方块高度为\(A_i\) 你可以决定每次会下落一个\(1 \times K\)或者\(K \times 1\)的方块 你 ...

  2. 【CF1142B】Lynyrd Skynyrd

    [CF1142B]Lynyrd Skynyrd 题面 洛谷 题解 假设区间\([l,r]\)内有一个循环位移,那么这个循环位移一定有一个最后的点,而这个点在循环位移中再往前移\(n-1\)个位置也一定 ...

  3. sql 将字符串转化为table

    /* *参数说明: 第一个参数为要转化的字符串,第二个参数为字符串中间的分隔符 */ ),)) )) as begin ) set @SourceSql=@SourceSql+@StrSeprate ...

  4. 分类模型的评价指标Fscore

    小书匠深度学习 分类方法常用的评估模型好坏的方法. 0.预设问题 假设我现在有一个二分类任务,是分析100封邮件是否是垃圾邮件,其中不是垃圾邮件有65封,是垃圾邮件有35封.模型最终给邮件的结论只有两 ...

  5. 关于C++中extern的简单笔记

    extern可以实现多文件共享同一个变量.const常量.函数. 下面结合几个例子来讲一下extern的相关性质(下述皆为多文件编译): 例1: //file1.cpp #include<ios ...

  6. GoCN每日新闻(2019-11-01)

    GoCN每日新闻(2019-11-01) GoCN每日新闻(2019-11-01) 1. Rob Pike 认为 Go 成功的 5 个因素 https://changelog.com/posts/5- ...

  7. pytest + allure 生成测试报告

    pytest测试样例规则:测试文件以test_开头(以_test结尾也可以)测试类以Test开头,并且不能带有 init 方法测试函数以test_开头断言使用基本的assert即可 ubuntu 安装 ...

  8. Pytorch中ndarray tensor list互转

    1.ndarray->tensor : b=torch.from_numpy(a) 2.tensor->ndarray: b=a.numpy() ''' 但这么写会报错-- Runtime ...

  9. docker之网络桥接的两种方式

    第一种:直接敲命令方式配置安装网桥管理工具包:bridge-utile # yum install bridge-utils -y 1.先查看ip 是否有br0ip a2.brctl show 3使用 ...

  10. CentOS7安装及配置vsftpd (FTP服务器FTP账号创建以及权限设置)

    本文章向大家介绍CentOS7安装及配置vsftpd (FTP服务器FTP账号创建以及权限设置),主要包括CentOS7安装及配置vsftpd (FTP服务器FTP账号创建以及权限设置)使用实例.应用 ...