redis源码分析(一)-sds实现
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)
它的大致步骤可描述如下:
- 根据c_str的长度选择合适的sds头部类型,这一步由sdsReqType()函数实现
- 分配足够的空间存储头部与有效字符串(sds字符串末尾需要一个字节存储’\0’),这一步由s_malloc()函数实现。
- 设置头部信息,将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)
它的操作步骤大致如下:
- 查看sds中是否有足够的剩余空间容纳addlen长度的字符串,有则返回,无则继续其它操作。
- 计算需要重新分配的存储空间的长度,包括原sds长度与addlen,另外预备一部分的剩余空间。
- 根据新的长度,得到新的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)
大致操作步骤如下:
- 根据len计算新的sds类型与所需内存空间。
- 如果新的类型与原sds类型相同,使用s_realloc压缩原内存空间。
- 如果新的类型与原sds类型不相同, 使用s_alloc重新分配空间,将原内容copy到新分配的sds中,并释放原内存。
sdsRemoveFreeSpace的实现与sds sdsMakeRoomFor大致相同,此处不再列出。
另外sds还提供了很多工具函数,如cat操作,copy操作,ll2str(整数转字符串),vprintf操作(格式化操作)等。具体实现见源码文件sds.c。
redis源码分析(一)-sds实现的更多相关文章
- Redis源码分析(sds)
源码版本:redis-4.0.1 源码位置:https://github.com/antirez/sds 一.SDS简介 sds (Simple Dynamic String),Simple的意思是简 ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
- redis源码分析之事务Transaction(下)
接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...
- redis源码分析之有序集SortedSet
有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...
- Redis源码分析(intset)
源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...
- Redis源码分析(dict)
源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...
- redis源码分析之发布订阅(pub/sub)
redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...
- [Redis源码阅读]sds字符串实现
初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...
- redis源码分析之事务Transaction(上)
这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...
随机推荐
- 1.创建SpringMVC项目
1.搭建环境 在Configure下点击Settings 找到Maven修改配置目的是使用自己配置的Maven以及阿里云镜像 开始创建项目 写入自己的公司名,项目名点击下一步 添加信息加快maven配 ...
- Vuejs发送Ajax请求
一.概况 ①vuejs中没有内置任何ajax请求方法 ②在vue1.0版本,使用的插件 vue resource 来发送请求,支持promise ③在vue2.0版本,使用社区的一个第三方库 axio ...
- 关于STM32 Flash的一些问题
注:本人感觉是STM32 Flash本身的问题. 最近做STM32的远程升级,保存到Flash里面,用于记录更新状态的信息总是无故的清理掉 最终测试发现 STM32的 Flash 擦除操作 并不一定会 ...
- Java实现PV操作 | 哲学家进餐问题
运行结果: Java代码: public class Main { public static void main(String[] args) { Global global=new Global( ...
- 求等差数列前$n$项和$S_n$的最值
一.方法依据: 已知数列\(\{a_n\}\)是等差数列,首项为\(a_1\),公差为\(d\),前\(n\)项和为\(S_n\),则求\(S_n\)的最值常用方法有两种: (1).函数法:由于\(S ...
- pycharm配置默认代码和注释模板
有人问,在pycharm新建python文件时,文件开头的注释每次都要重复写,能不能配置成模板?其实pycharm本身自带此功能 在pycharm菜单栏找File -> settings -&g ...
- Java解决java.io.FileNotFoundException: E:\work\work (拒绝访问。)
一.问题 在使用FileInputStream或FileOutputStream时会遇到如下问题1和问题2. 问题1: java.io.FileNotFoundException: .\xxx\xxx ...
- [Beta阶段]第十一次Scrum Meeting
Scrum Meeting博客目录 [Beta阶段]第十一次Scrum Meeting 基本信息 名称 时间 地点 时长 第十一次Scrum Meeting 19/05/20 大运村寝室6楼 15mi ...
- .Net Core 发送https请求/.net core 调用数字证书 使用X509Certificate2
.Net Core 发送https请求 .net core 调用数字证书 使用X509Certificate2 .NET下面的 .netfromwork使用和asp.net core下使用方式不一样 ...
- 【深入学习linux】Linux系统安装
1. 配置内存大小,和设置镜像文件,开启虚拟机,点击虚拟机,立即按F2,会出现下图 2. 选择 Boot 菜单,默认是以硬盘进行启动,但是硬盘目前为空,则不能启动,需要改成以光盘形式启动即 CD-RO ...