redis 系列3 数据结构之简单动态字符串 SDS

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 原文:redis 系列3 数据结构之简单动态字符串 SDS一.  SDS概述   Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。
原文: redis 系列3 数据结构之简单动态字符串 SDS

一.  SDS概述

  Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。Redis只会使用C字符串作为字面量。在Redis里,使用SDS来表示字符串值,是一个可以被修改的字符串,字符串“键值对”底层都是由SDS实现的。

-- 例1:客户端执行如下命令:
127.0.0.1:6379> set msg "hello world"
OK
127.0.0.1:6379> get msg
"hello world"

  上面例1中就在数据库里创建一个新的键值对。 其中“键”是一个字符串对象,对象的底层实现是一个保存着字符串"msg" 的SDS。 "键值" 也是一个字符串对象,对象的底层实现是一个保存着字符串" hello world " 的SDS。

-- 例2: 客户端执行如下命令:
127.0.0.1:6379> rpush  fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> lrange fruits 0 -1
1) "apple"
2) "banana"
3) "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实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
11天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
32 6
|
21天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
25天前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
54 8
|
25天前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
29天前
|
NoSQL Redis
Redis 字符串(String)
10月更文挑战第16天
37 4
|
1月前
|
消息中间件 存储 缓存
redis支持的数据结构
redis支持的数据结构
30 2
|
20天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
21天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2月前
|
存储 缓存 NoSQL
3)深度解密 Redis 的字符串
3)深度解密 Redis 的字符串
32 1
|
2月前
|
存储 JSON NoSQL
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
这篇文章是关于Redis基本数据结构的学习笔记,包括了String、Hash、Set、List和SortedSet的介绍和常用命令。文章解释了每种数据结构的特点和使用场景,并通过命令示例演示了如何在Redis中操作这些数据结构。此外,还提供了一些练习示例,帮助读者更好地理解和应用这些数据结构。
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】