Redis中Srting类型的底层实现-SDS

Redis中使用SDS存储字符串,而非C语言的char数组,因为SDS提供了长度快速获取、防止缓冲区溢出、空间预分配等优势。SDS在字符串增长和减少时,能有效减少内存重分配次数,且支持二进制安全,适用于多种数据存储需求。

推荐阅读:

1、什么是SDS?

sds是c中的一个数据结构,如下:

struct sdshdr{
    //buf数组中已使用的字节数,即字符串长度
    int len;
    //buf数组未使用的字节数
    int free;
    //存储字节的数组
    int[] buf;
}

redis中用这种数据结构来保存string类型

2、为什么redis要用SDS来存储字符串?

c/c++ 中 char 数组也可以存储字符串,而且c就是这么干的, C 语言使用长度为 N+1 的字符数组来表示长度为 N 的字符串, 并且字符数组的最后一个元素总是空字符 ‘\0’ 。

我们分析一下以下几种情况char数组和sds的区别:

2.1. 长度的获取。

c中字符串长度获取需要遍历char数组,时间复杂度是O(n)

sds直接记录了字符串的长度,所以长度获取的时间复杂度是O(1)

2.2. 缓冲区溢出(buffer overflow)

除了获取字符串长度的复杂度高之外, C 字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)。 <string.h>/strcat 函数可以将 src 字符串中的内容拼接到 dest 字符串的末尾,当dest拼接的src的长度超过自身分配的地址大小时,就会修改到别的地址,造成缓冲区溢出。

SDS 的空间分配策略完全杜绝了发生缓冲区溢出的可能性: 当 SDS API 需要对 SDS 进行修改时, API 会先检查 SDS 的空间是否满足修改所需的要求, 如果不满足的话, API 会自动将 SDS 的空间扩展至执行修改所需的大小, 然后才执行实际的修改操作, 所以使用 SDS 既不需要手动修改 SDS 的空间大小, 也不会出现前面所说的缓冲区溢出问题。

2.3. 空间预分配

空间预分配用于优化 SDS 的字符串增长操作: 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。

其中, 额外分配的未使用空间数量由以下公式决定:

如果对 SDS 进行修改之后, SDS 的长度(也即是 len 属性的值)将小于 1 MB , 那么程序分配和 len 属性同样大小的未使用空间, 这时 SDS len 属性的值将和 free 属性的值相同;

如果对 SDS 进行修改之后, SDS 的长度将大于等于 1 MB , 那么程序会分配 1 MB 的未使用空间。

举个例子: 如果进行修改之后, SDS 的 len 将变成 13 字节, 那么程序也会分配 13字节的未使用空间, SDS 的 buf 数组的实际长度将变成: 13 + 13 + 1 = 27 字节(额外的一字节用于保存空字符)。

如果SDS 的 len 将变成 30 MB , 那么程序会分配 1 MB 的未使用空间, SDS 的 buf 数组的实际长度将为 30 MB + 1 MB + 1 byte 。 通过空间预分配策略, Redis 可以减少连续执行字符串增长操作所需的内存重分配次数。

2.4. 惰性空间释放

简单的说,sds在使用sds api减少字符串长度的时候,并不会立马释放缩短后多出来的字节,万一下次又用到了呢,是吧。

2.5. 二进制安全

C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

虽然数据库一般用于保存文本数据, 但使用数据库来保存二进制数据的场景也不少见, 因此, 为了确保 Redis 可以适用于各种不同的使用场景, SDS 的 API 都是二进制安全的(binary-safe): 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据, 程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样。

这也是我们将 SDS 的 buf 属性称为字节数组的原因 —— Redis 不是用这个数组来保存字符, 而是用它来保存一系列二进制数据。

3、SDS的启发。

不要局限于现有的数据结构,多思考一下,结合实际应用场景选择符合自己的最佳结构。

空间换时间真是个不错的选择。

### 回答1: 在 Redis 中,String 是一种基本的数据类型,它可以存储字符串、整数或者浮点数等类型的数据。 String 的底层实现是一个字节数组,Redis 会根据存储的内容来判断该字符串是一个普通的字符串、整数或者浮点数,并相应地进行编码和存储。 在存储字符串时,Redis 会根据字符串的长度来选择不同的编码方式,可以选择使用普通的字符串编码,也可以使用一种叫做 intset 的紧凑编码方式,intset 可以节省空间,但是仅适用于存储较小的整数。 在存储整数或浮点数时,Redis 会将其转换成二进制格式并存储在字节数组中,这样可以节省存储空间,并且提高访问速度。 除了普通的字符串操作外,Redis 还提供了一些针对 String 类型的特殊操作,如增加或减少一个整数、获取一个子串等。 ### 回答2: Redis 基础类型中的 String 底层实现SDS(Simple Dynamic String)。SDSRedis 自己实现的、用于替代 C 语言中的传统字符串的结构,它在字符串的操作上有很大的优化。 SDS字符串的长度和内容分开存储,它的数据结构如下: ``` struct sdshdr { int len; // 字符串长度 int free; // 剩余空间长度 char buf[]; // 字符串内容 }; ``` SDS 的优点主要体现在以下几个方面: 1. 获取字符串长度的时间复杂度为 O(1)。由于 SDS 将长度信息存储在结构体中,我们无需遍历整个字符串才能获取长度,这在某些操作中非常有用。 2. 杜绝缓冲区溢出问题。SDS 会在字符串内容后面留出空间,可以防止字符串内容超出预分配的空间导致的内存溢出问题。 3. 减少字符串扩容时带来的性能开销。当字符串长度超过 SDS 分配的空间时,SDS 会自动进行扩容,而不是每次进行增量扩容,避免了频繁的增加内存的开销。 4. 支持惰性空间释放。当 SDS 进行缩容操作时,不会立即释放多出来的空间,而是将空白部分标记为"free",等待下次进行利用。 总而言之,Redis 中的 String 类型底层实现采用了自己设计的 SDS 结构,它在性能、空间利用和安全性等方面都有很大的优势。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值