谈一谈你对Redis中简单动态字符串的理解?
2 个回答
慢慢理解世界,慢慢更新自己。java蚂蚁在 Redis 中,**简单动态字符串(SDS, Simple Dynamic String)** 是 Redis 用于管理字符串的核心数据结构。与传统的 C 语言字符串(以空字符 `\0` 结尾的字符数组)不同,SDS 提供了更灵活、更高效的字符串管理方式。SDS 不仅支持动态扩展,还避免了传统 C 字符串操作中常见的内存溢出问题。接下来我将详细解释 SDS 的实现和它的特点。 ### SDS 的结构 Redis 的 SDS 是一种包含元数据的字符串结构,其定义如下: ```c struct sdshdr { int len; // 记录已使用的字符串长度 int free; // 记录字符串未使用的空间长度 char buf[]; // 实际存储字符串的字符数组 }; ``` - **`len`**:记录当前字符串的长度,也就是 `buf` 中已使用的字节数。 - **`free`**:记录 `buf` 中未使用的剩余空间长度。这部分空间可以用来动态扩展字符串。 - **`buf[]`**:实际存储字符串的字符数组,尾部有一个 `\0` 作为终结符。 相比于 C 语言中的字符串,SDS 的这种设计使得字符串的长度和空余空间都可以被直接获取,不需要像 C 字符串那样调用 `strlen` 遍历整个字符串去计算长度。 ### SDS 的特点 #### 1. **动态扩展** SDS 是动态的,能够根据需要自动扩展或缩减空间。例如,当你向一个 SDS 字符串中追加数据时,如果原有空间不足,SDS 会自动分配更多内存来存储新数据。 Redis 中的 SDS 使用了“空间预分配”策略: - 如果新追加的字符串长度小于 1MB,SDS 会按双倍扩展内存。 - 如果新追加的字符串长度大于等于 1MB,SDS 只会扩展 1MB 的内存。 通过这种预分配策略,SDS 可以减少频繁的内存分配操作,从而提高性能。 #### 2. **防止缓冲区溢出** 由于 SDS 结构中记录了 `len` 和 `free`,Redis 在进行字符串操作时可以随时检查是否有足够的空间来存储新的数据,避免了 C 语言中常见的缓冲区溢出问题。 #### 3. **二进制安全** SDS 支持二进制数据,允许在字符串中存储任意字节数据,包括 `\0` 字符。在 C 语言的字符串中,`\0` 是字符串的结束符,而 SDS 中由于使用 `len` 记录字符串长度,因此可以正确处理包含 `\0` 字符的数据,这使得 SDS 具有“二进制安全性”。 #### 4. **空间回收** 当 SDS 中的字符串被缩短时,SDS 并不会立即释放缩短后多余的空间,而是通过增加 `free` 字段的值来记录空闲空间。这样当后续有新的字符串追加时,可以直接使用这些空闲空间,避免频繁的内存重新分配。 #### 5. **常数时间获取长度** 与 C 语言的字符串不同,SDS 可以在常数时间 O(1) 内获取字符串长度,因为 SDS 直接使用 `len` 字段记录字符串的长度,而不需要遍历字符串计算长度。 ### 例子:SDS 的使用 假设我们有一个 SDS 对象,它的初始状态如下: - `len = 5` - `free = 10` - `buf = "hello"` SDS 结构的内存布局为: ``` +------+-------+----------------+ | len | free | buf | +------+-------+----------------+ | 5 | 10 | h e l l o \0 | +------+-------+----------------+ ``` 如果我们向这个字符串追加 `"world"`,则 SDS 的状态会变为: - `len = 10` (表示字符串长度为 10) - `free = 5` (原有的剩余空间减少了) - `buf = "helloworld"` 内存布局为: ``` +------+-------+----------------+ | len | free | buf | +------+-------+----------------+ | 10 | 5 | h e l l o w o r l d \0 | +------+-------+----------------+ ``` ### SDS 的优势 - **性能优化**:通过动态扩展和空间预分配,SDS 减少了频繁的内存分配和字符串拷贝,提升了 Redis 的性能。 - **安全性**:避免了 C 语言中的缓冲区溢出问题,同时 SDS 具有二进制安全性,支持存储任意二进制数据。 - **方便使用**:通过 `len` 和 `free` 字段,SDS 可以在 O(1) 时间内获取字符串长度,并且在追加数据时可以有效管理空闲空间。 ### 总结 SDS 是 Redis 中字符串操作的核心数据结构,它相比于传统的 C 字符串有显著的性能和安全性优势。SDS 的动态扩展、二进制安全、空间预分配等特点使得它特别适合 Redis 这样高效、需要频繁字符串操作的应用场景。发布于 2023-03-07 08:12 •IP属地 江苏举报评论收藏
鸟随鸾凤飞腾远,人伴贤良品自高。无邪Redis中的简单动态字符串其实是对C语言中的字符串的封装和优化, ### 因为C语言的字符串有两个缺点: 1.不是二进制安全的(因为字符串以空字符作为结束的标志,字符串中间不能有空字符,Redis的简单动态字符串是一个结构体,有一个变量存储了字符串的长度,所以不需要以空字符作为结束的标志)。 从定义上来看,二进制安全是一种主要用于字符串操作函数相关的计算机编程术语。一个二进制安全功能(函数),其本质上将操作输入作为原始的、无任何特殊格式意义的数据流。对于每个字符都公平对待,不特殊处理某一个字符。 简单来说C语言使用\0字符来作为字符串结束符是不符合这种二进制安全的规范的。 2.频繁修改一个字符串时,会涉及到内存的重分配,比较消耗性能。(Redis中的简单动态字符串会有内存预分配和惰性空间释放)。 **如果字符串实际使用长度len<1M**,实际分配空间=len长度来存储字符串+1字节存末尾空字符+len长度的预分配空闲内存 **如果字符串实际使用长度len>1M**,实际分配空间=len长度来存储字符串+1字节存末尾空字符+1M长度的预分配空闲内存 所以Redis中的简单动态字符串结构,除了包含一个字符数组的属性,还包含数组的长度,数组的实际使用长度等属性,通过增加长度属性,可以保证字符串是二进制安全的,从而可以保存任意类型的数据,例如一张图片,对象序列化后的数据等等。 **字符串使用场景如下:** 1.字符串可以保存一些字符串数据,也可以保存一些数字类型的数据,所以可以使用INCR, DECR, INCRBY对数字进行加减,所以可以把字符串当成计数器使用。 2.同时因为在C语言中,每个字符是一个字节,是8个二进制位,所以可以把简单动态字符串作为一个位数组来使用,通过setbit,getbit命令来对位数组进行赋值,取值,可以以很小的空间来保存用户一年的每日签到数据,以及Redis中的布隆过滤器也是通过位数组来实现的。 ### 字符串的底层存储 在Redis中,每一个Value都是一个Redis对象,对应的都是RedisObject结构,在RedisObject结构中,保存了对象的类型type,底层的编码encoding等一些属性,也拥有一个ptr指针,指向对象具体的存储地址。 ```java struct RedisObject { int4 type; //类型 int4 encoding; //编码 int24 lru; int32 refcount; void *ptr; } robj; ``` 在Redis中,字符串有两种存储方式,int编码,embstr编码和raw编码。 **int编码** 当value是一个整数,并且可以使用long类型(8字节)来表示时,那么会属于int编码,ptr直接存储数值。(并且Redis会进行优化,启动时创建0~9999的字符串对象作为共享变量。) **embstr和raw编码** 两种存储方式下,都RedisObject和SDS结构(简单动态字符串)来存储字符串,区别在于,embstr对象用于存储较短的字符串,embstr编码中RedisObject结构与ptr指向的SDS结构在内存中是连续的,内存分配次数和内存释放次数均是一次,而raw编码会分别调用两次内存分配函数来分别创建RedisObject结构和SDS结构。发布于 2022-02-11 23:46 •IP属地 辽宁举报评论收藏1
公众号进入网站体验更佳

