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

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

推荐阅读:

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 结构,它在性能、空间利用和安全性等方面都有很大的优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值