redis 系列3 数据结构之简单动态字符串 SDS
一. SDS概述
Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。Redis只会使用C字符串作为字面量。在Redis里,使用SDS来表示字符串值,是一个可以被修改的字符串,字符串“键值对”底层都是由SDS实现的。
-- 例1:客户端执行如下命令:
127.0.0.1:> set msg "hello world"
OK
127.0.0.1:> get msg
"hello world"
上面例1中就在数据库里创建一个新的键值对。 其中“键”是一个字符串对象,对象的底层实现是一个保存着字符串"msg" 的SDS。 "键值" 也是一个字符串对象,对象的底层实现是一个保存着字符串" hello world " 的SDS。
-- 例2: 客户端执行如下命令:
127.0.0.1:> rpush fruits "apple" "banana" "cherry"
(integer)
127.0.0.1:> lrange fruits -
) "apple"
) "banana"
) "cherry"
上面例2中也在数据库里创建一个新的键值对。其中“键”是一个字符串对象,对象的底层实现是一个保存着字符串" fruits " 的SDS。"键值"是一个列表对象,列表包含了三个字符串对象,分别由三个SDS实现。
SDS除了用来保存数据库中的字符串值之外,还用作缓冲区(buffer): AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区。
二. SDS 定义
每个SDS.h文件下的sdshdr结构表示一个SDS值, 下面是Redis源码,在github的地址是https://github.com/antirez/sds
struct sdshdr{
//记录buf数组中已使用字节的数量,也就是字符串的长度
int len ;
// 记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
在C语言中使用长度为N+1的字符数组来表示长度为N的字符串,并且字符数组最后一个元素总是空字符'\0'。 假设SDS的值为 "Redis",那么free属性值为0, len 属性值为5, buf数组为R,e,d,i,s五个字符,最后一个字节则保存空字符'\0' 。
三. SDS与C字符串的区别
C语言使用简单的字符串表示方式,并不能满足Redis对字符串在安全性,效率以及功能方面的要求,从几个方面说明:
1. 常数复杂度获取字符串长度
因为c字符串并不记录自身的长度信息,所以为了获取一个c字符串的长度,程序必须遍历整个字符串。与C 字符串不同,因为SDS在len属性中记录了SDS本身的长度,对于SDS的值为"Redis"的字节长度就是5。
2. 杜绝缓冲区溢出
在c中, 假设紧邻的字符串s1 和 s2, s1保存为"redis", s2保存为"mongodb", 如果修改s1的值为 redis cluster, 但修改之前没了空间,那么s1的数据将溢出到s2所在空间中。
在SDS中,会先检查给定的SDS空间是否足够,会自动扩展修改所需的大小空间。然后在执行实际的修改操作。
3. 减少修改字符串时带来的内存重分配次数
在c 中,字符串的底层实现总是一个N+1个字符长的数组,因为字符串的长度和底层数组的长度之间存在着这种关联,所以每次增长或者缩短一个c字符串,程序都要对保存这个C字符串的数组进行一次内存重分配操作。
在SDS中通过未使用空间解除了字符串长度和底层数组长度之间的关联,buf数组的长度不一定就是字符数量加1, 数组里机可以包含未使用的字节,这些由free属性记录。
3.1 空间预分配
当SDS的API对一个SDS进行操作,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS 分配修改所必须要的空间,还会为SDS分配额外的未使用空间。额外分配的未使用空间数量由以下公式决定:
如果对SDS进行修改之后,SDS的长度(也即是len属性的值) 将小于1MB,那么程序分配和len属性同样大小的未使用空间。这时SDS len属性的值将和fee属性的值相同,例如:修改之后,SDS的len将变成13字节, 那么程序也会分配13字节的未使用空间,SDS的buf数组的实际长度将变成13+13+1=27字节。
如果对SDS进行修改之后,SDS的len大于等于1MB, 那么程序会分配1MB的未使用空间,如果对SDS进行修改之后, SDS的len变成30MB,那么程序会分配1MB的未使用空间,SDS的buf数组的实际长度为30MB + 1MB +1byte。
通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数。
3.2 惰性空间释放
惰性空间释放用于优化SDS的字符串缩短操作。当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短多出来的字节,而是使用free属性将这些字节的数据记录起来,并等待将来使用(缩短后未使用的空间不会释放,而是将来增长操作时,再使用这些未使用空间)。
4. 二进制安全
在c字符串的字符必须符合某种编码(如ASCII), 并且除了字符串的末尾之处,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误以为是字符串的结尾,这使得c字符串只能保存文本数据,不能保存图片,音频,视频,压缩文件之类的二进制数据。
为了保证Redis 可以适用各种不同的使用场景,SDS的API 都是二进制安全的,程序不会对其中的数据做任何限制,过滤,数据写入是什么,读取时就是什么。
5. 兼容部分C字符串函数
在SDS中会遵循C字符串以空字符结尾的惯例,总会为buf数组分配空间时多分配一个字节来容纳这个空字符,这是为了让SDS的字符串可以重用一部分(string.h>库定入的函数。
四 总结
4.1 C字符串与SDS之间的区别总结
C字符串 |
SDS |
获取字符串长度的复杂度为0(N) |
获取字符串长度的复杂度为0(1) |
API是不安全的,可能会造成缓冲区溢出 |
API是安全的,不会造成缓冲区溢出 |
修改字符串长度N次必然需要执行N次内存重分配 |
修改字符串长度N次最多需要执行N次内存重分配 |
只能保存文本数据 |
可以保存文本或者二进制数据 |
可以使用所有<string.h>库中函数 |
可以使用一部分<string.h>库中函数 |
4.2 SDS API(主要的一些API)
函数 |
作用 |
sdsnew |
创建一个SDS字符串 |
sdsempty |
创建一个不包含内容的空SDS 字符串 |
sdsfree |
释放给定的SDS字符串未使用空间 |
sdslen |
返回SDS字符串已使用空间字节数 |
sdsavail |
返回SDS字符串未使用空间字节数 |
sdsdup |
创建一个给定SDS的副本 |
sdsclear |
清空SDS字符串内容 |
sdscat |
将给定c字符拼接到SDS字符串的末尾 |
sdscatsds |
将给定的SDS字符串拼接到另一个SDS字符串的末尾 |
sdscpy |
将给定的c字符拼复制到SDS里机,覆盖SDS原有字符串 |
sdsgrowzero |
用空字符将SDS扩展至指定长度 |
sdsrange |
保留SDS指定区间内的数据,不在区间内的数据会被覆盖或清除 |
sdstrim |
接受一个SDS和一个C字符串作为参数,从SDS中移除所有在C字符串中出现过的字符 |
sdscmp |
对比两个SDS字符串是否相同 |
redis 系列3 数据结构之简单动态字符串 SDS的更多相关文章
- Redis数据结构之简单动态字符串SDS
Redis的底层数据结构非常多,其中包括SDS.ZipList.SkipList.LinkedList.HashTable.Intset等.如果你对Redis的理解还只停留在get.set的水平的话, ...
- Redis 数据结构之简单动态字符串SDS
几个概念1:key对象 数据库存储键值对的键,总是一个字符串对象.2:value对象 数据库存储键值对的值,可以是字符串对象,list对象,hash对象,set对象,sorted set对象. ...
- Redis源码阅读一:简单动态字符串SDS
源码阅读基于Redis4.0.9 SDS介绍 redis 127.0.0.1:6379> SET dbname redis OK redis 127.0.0.1:6379> GET dbn ...
- 图解Redis之数据结构篇——简单动态字符串SDS
图解Redis之数据结构篇--简单动态字符串SDS 前言 相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...
- Redis底层探秘(一):简单动态字符串(SDS)
redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redi ...
- 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等
redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...
- 深入理解Redis 数据结构—简单动态字符串sds
Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...
- Redis数据结构之简单动态字符串
Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将SDS用作Redi ...
- 【Redis】简单动态字符串SDS
C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"; // 也可以添加 ...
随机推荐
- 对java web开发的理解
写完后发现自己好像很多都不知道,管它的,只要自己了解就行了,以后有时间再把这个完善一下吧 感觉就是三层架构翻来覆去的用,以这个为基础不停的扩展,前提是数据量一定要大,不然这些扩展就没啥意思 这是数据量 ...
- 小白的CTF学习之路8——节约内存的编程方式
今天第二更,废话不说上干货 上一章我们学习了内存和cpu间的互动方式,了解到内存的空间非常有限,所以这样就需要我们在编程的时候尽可能的节省内存空间,用最少的空间发挥最大的效果,以下是几种节约内存的方法 ...
- PAT DFS,BFS,Dijkstra 题号
为什么要分类刷题: 因为刷⼀道算法题需要花⼀两个⼩时甚⾄半天,平时我们还要上课做别的事情,你在⼀段时间内刷算法如果只按照顺序,可能今天遇到了⼀道最短路径的题⽬,弄了半天好不容易看懂了别⼈的代码,以为⾃ ...
- redis_哈希对象
redis哈希对象的底层编码有两种:ziplist.hashtable ziplist编码 当一个哈希键只包含少量kv对.且key和value都是小整数值.短字符串时,redis会使用压缩列表来做 z ...
- Spring PropertyEditor Spring conversion框架分析
PropertyEditor https://blog.csdn.net/pentiumchen/article/details/44026575 conversion https://blog.cs ...
- spring InitializingBean和DisposableBean init-method 和destroy-method @PostConstruct @PreDestroy
对于初始化函数: @PostConstruct 注解的方法 InitializingBean接口定义的回调afterPropertiesSet() Bean配置中自定义的初始化函数 对于析构则与上相同 ...
- struts2 升级至2.3.32时访问页面报错 File "/struts-tags" not found
Apache struts是美国阿帕奇(Apache)软件基金会负责维护的一个开源项目,是一套用于创建企业级Java Web 应用的开源MVC框架,主要提供两个版本框架产品: struts 1和str ...
- karaf 控制台 常用linux指令(2)
11,查看onos风格注解实例列表 -bash代码 scr:list ACTIVE代表实例已生成,REGISTERED代表实例未注入生成 12,查看组件列表,查看组件信息,查看组件提供的服务 -bas ...
- django 图书管理系统
一.图书管理系统 单表的增删改查 1.创建项目 2.注释掉中间件 就可以提交post 请求 3.配置静态文件 并手动创建static 文件夹存放静态文件 二.具体的数据库配置 1.创建数据库 2. ...
- repo跟svn的区别
Git与SVN区别 Git和SVN正好相反,git提倡开发时拉分支,各干各的,相互独立,发版本时打标签:而svn呢,平时大家都在主干上干活,发版本时拉个分支,所以呢,svn经常会提交冲突,经常要合并代 ...