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"; // 也可以添加 ...
随机推荐
- <taglib>报错
问题:web.xml中<taglib>报错 2.3版本可以直接卸载<web-app>中 2.4及之后放在<jsp-config>中 <jsp-config&g ...
- python基础入门之对文件的操作
**python**文件的操作1.打开文件 打开文件:open(file,mode='r') file:操作文件的路径加文件名 #绝对路径:从根目录开始的 #相对路径:从某个路径开始 mode:操作文 ...
- CentOS7安装特定版本的Docker
查询可用版本 [root@bogon ~]# yum list docker-ce --showduplicates | sort -r 查询结果 * updates: centos.ustc.edu ...
- asp.net core web 项目附加进程调试
之前asp.net web项目在部署IIS站点的时候可以直接选择项目目录,不用发布,然后附加进程的时候,找到w3wp.exe开头的进程,再根据用户名找到要附加的进程,就可以附加进程调试了.但asp.n ...
- MySQL中的latch(闩锁)详解——易产生的问题以及原因分析
Latch 什么是latch: 锁是数据库系统区别与文件系统的一个关键特性.锁机制用于管理对共享资源的并发访问.Innodb存储引擎在行级别上对表数据上锁,这固然不错.但是Innodb也会在多个地方使 ...
- vi/vim 三种模式的操作
来源:http://www.runoob.com/linux/linux-vim.html ps:刚刚进入vi/vim 是命令模式 一.命令模式 i 切换到输入模式,以输入字符. x 删除当前光标所在 ...
- vue + spring boot + spring security 前后端分离 携带 Cookie 登录实现 只写了个登录
最近想弄一下vue 所以就自己给自己找坑入 结果弄的满身是伤 哈哈哈 首先我说下 前后端分离 跨域请求 我在网上找了一些 可是都是针对于 spring boot 的 我自己还有 securi ...
- Python开发——8.模块
一.模块 1.模块 (1)定义:一个.py文件就是一个模块 (2)原因:为了防止程序代码越来越长,对函数进行分组放到不同的文件夹里. (3)优点:提高代码的可维护性:模块编写完毕可以被别人引用,也可以 ...
- Java WebService 简单实例使用JDK
原文地址:http://www.cnblogs.com/jasoncc/archive/2011/12/22/2296052.html 什么是WebServices? 它是一种构建应用程序的普 ...
- Maven搭建SSH框架
工具:Eclipse(Maven管理工具)+Tomcat+Mysql. 1.新建一个Maven工程(maven-archetype-webapp). 打开File ——>new——>Mav ...