Redis数据结构之简单动态字符串SDS
Redis的底层数据结构非常多,其中包括SDS、ZipList、SkipList、LinkedList、HashTable、Intset等。如果你对Redis的理解还只停留在get、set的水平的话,是远远不足以应对面试提问的。本文简单介绍了Redis底层最重要的数据结构 - 简单动态字符串(SDS)
Redis使用C语言开发,但并没有使用C语言传统的字符串表示(以空字符结尾的字节数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串的(simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。
在Redis里面,C字符串只会作为字符串字面量(static literal)用在一些无须对字符串值进行修改的地方。当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串的键值对在底层都是由SDS实现的。
咱们来举个例子,如果在客户端执行命令:
redis> SET msg "hello world"
ok
那么Redis将在数据库中创建一个新的键值对,其中:
- 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串“msg”的SDS。
- 键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串“hello world”的SDS。
除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区:AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的。总之,SDS是Redis的最基础也是最重要的数据结构。
1.SDS的定义
每个 sds.h/sdshdr 结构表示一个SDS值:
struct sdshdr{
// 记录buf数组中已使用字节的数量
// 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
用一张图来表示:

SDS 遵循 C 字符串以空字符结尾的惯例, 保存空字符的 1字节空间不计算在 SDS 的 len 属性里面, 并且为空字符分配额外的 1 字节空间, 以及添加空字符到字符串末尾等操作都是由 SDS 函数自动完成的, 所以这个空字符对于 SDS 的使用者来说是完全透明的。
2.SDS与C字符串的区别
现在来说,C语言使用长度为N+1的字符数组来表示长度为N的字符串,并且字符数组的最后一个元素总是空字符“\0”。
C的这种简单的字符串表达方式,并不能满足Redis对字符串在安全性、效率以及功能方面的要求。具体有以下几个方面。
2.1 常数复杂度获取字符串长度
因为C字符串并不记录字符串的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的每个字符进行计数,直到遇到空字符为止,这个操作的复杂度为O(n)。而在Redis的SDS中,这个时间复杂度只有O(1)。
2.2 杜绝缓冲区溢出
除了获取字符串长度的复杂度高之外,C字符不记录自身长度带来的另一个问题就是缓冲区溢出。举个例子,C语言的 strcat 函数可以将字符串中的内容拼接到 dest 字符串的末尾,但是当字符串的容量不够就会产生缓存区溢出,因为字符串也是基于数组实现的,也是有大小限制的。
Redis的SDS已经杜绝了这个问题,那它是如何解决的呢?
当API要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的空间,如果不够的话,API会自动将SDS的空间进行扩容,然后才执行实际的修改操作。这就避免了缓冲区内存溢出。
2.3 减少修改字符串时带来的内存重分配次数
上面说到了API会在修改SDS字符串时自动扩容,如果每次修改都伴随着对字符串内的数组的内存重分配,那效率可想而知。所以Redis实现了空间预分配和惰性空间释放两种优化策略。
空间预分配
空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所需要的空间,还会为SDS分配额外的未使用空间。
总的来说,额外分配的未使用空间数量大小有两种可能:
- 如果对SDS修改之后,SDS的长度将小于1MB,那么程序分配和len 属性同样大小的未使用空间,这时候SDS的 free 属性的值将和 len 属性的值相同。也就是说,该SDS字符串修改完后还有近一半的容量。
- 如果对SDS修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。这个是固定的。
通过空间预分配,Redis可以减少连续执行字符串操作所需的内存重分配次数。
惰性空间释放
惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录起来,并等待将来使用。
2.4 二进制安全
在C语言中,字符串的存储必须符合某种编码(ASCII),并且字符串不能包含空字符,否则会被认为是字符串结尾。这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
所以,为了解决C字符串的不足,Redis的 buf 数组保存的是二进制数据,这也就是把SDS的 buf 数组称为字节数组的原因。
2.5 兼容部分C字符串函数
虽然 Redis 的API都是二进制安全的,但它们一样遵循C字符串以空字符串结尾的惯例,这些API总会将SDS保存的数据的末尾设置为空字符,并且总会在为 buf 数组分配空间时多分配一个字节来容纳这个空字符,这是为了让那些保存文本数据的SDS可以重用一部分C的函数。
举个例子, 如果我们有一个 SDS 的指针 s , 那么我们可以直接使用 stdio.h/printf 函数, 通过执行以下语句:
printf("%s", s->buf);
来打印出 SDS 保存的字符串值 "Redis" , 而无须为 SDS 编写专门的打印函数。
最后,临近春节,祝大家新年快乐!
Redis数据结构之简单动态字符串SDS的更多相关文章
- Redis 数据结构之简单动态字符串SDS
几个概念1:key对象 数据库存储键值对的键,总是一个字符串对象.2:value对象 数据库存储键值对的值,可以是字符串对象,list对象,hash对象,set对象,sorted set对象. ...
- redis 系列3 数据结构之简单动态字符串 SDS
一. SDS概述 Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默 ...
- Redis数据结构之简单动态字符串
Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将SDS用作Redi ...
- Redis核心原理-简单动态字符串SDS
SDS简介 Redis是C语言编写的,但没有使用c语言的字符串结构,而是自己实现了一套简单动态字符串 simple dynamic string 简称SDS,SDS兼容C语言的字符串类型,原理类似Ja ...
- 图解Redis之数据结构篇——简单动态字符串SDS
图解Redis之数据结构篇--简单动态字符串SDS 前言 相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...
- Redis底层探秘(一):简单动态字符串(SDS)
redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redi ...
- 深入理解Redis 数据结构—简单动态字符串sds
Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...
- 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等
redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...
- 【Redis】简单动态字符串SDS
C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"; // 也可以添加 ...
随机推荐
- xshell连接虚拟机详解--技术流ken
xshell连接虚拟机 第一步:网络模式更改为桥接模式 第二步:重启网络 [root@ken1 ~]# systemctl restart network 第三步:获取IP地址 输入命令ip a 第四 ...
- java的ArrayList源码摘要
ArrayList本质上是一组对象数组,ArrayList有三种构造方法 1.指定长度创建ArrayList,2.默认长度为10创建.3,用旧的集合创建一个ArrayList. 对ArrayList的 ...
- FaaS技术框架
FaaS介绍 微服务(MicroService)是以专注于单一服务/功能的小型单元块为基础,利用模块化的方式组合成复杂的大型应用服务. FaaS是Function as a Service的缩写,可以 ...
- AJAX跨站处理解决方案
//直接使用ajax会提示跨站失败 $.ajax({ type : 'POST', url : 'http://www.abc.com/api', data : '', dataType : 'tex ...
- WEB前端常见面试题汇总:(一)
1.JS找字符串中出现最多的字符 例如:求字符串'nininihaoa'中出现次数最多字符 方法一: var str = "nininihaoa"; var o = {}; for ...
- Dynamics AX 2012 R2 电子邮件广播错误 0x80040213
Dynamics AX 2012 R2 电子邮件广播错误 0x80040213 今天Reinhard在新环境做邮件广播测试时,发现无法发送邮件,并报以下错误: 类"CDO.Message&q ...
- PHP技能树
- Easyui 修改|新增jquery-easyui icon图标
修改|新增jquery-easyui icon图标 by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 修改配置文件 打开jquery-easyui-1.5.3\ ...
- 在phpstudy中安装并使用ThinkPHP 5
最近在慕课网学习 thinkphp,由于教师使用的是 MAC下的 MAMP 环境,而我使用的是 win7 的 phpstudy,区别不大,记录在这里,方便查询. 不同系统集成环境安装: m ...
- UE3中的时间
为了管理时间,Unreal将游戏运行时间片分隔为"Ticks".一个Tick是关卡中所有Actors更新的最小时间单位.一个tick一般是10ms-100ms(CPU性能越好,游戏 ...