【Redis】数据结构与对象(六种底层数据结构)——简单动态字符串 SDS

简单来说,底层数据结构一共有 6 种,分别是简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。它们和数据类型的对应关系如下图所示:
在这里插入图片描述
String 类型的底层实现只有一种数据结构,也就是简单动态字符串。

简单动态字符串SDS

SDS是"simple dynamic string"的缩写。 Redis中所有场景中出现的字符串,基本都是由SDS来实现的:

  • 所有非数字字符串对象的key。例如set msg "hello world" 中的key msg.

  • 字符串数据类型的值。例如set msg "hello world" 中的msg的值"hello wolrd"

  • 列表对象类型中的“字符串值”。例如RPUSH fruits "apple" "banana" "cherry"中的"apple" "banana" "cherry"

SDS的作用:不仅可以用来保存数据库中的字符串值之外,SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区。

(1)SDS的定义

struct sdshdr {
    // 记录buf数组中已使用字节的数量,即SDS所保存字符串的长度
    unsigned int len;
    // 记录buf数据中未使用的字节数量
    unsigned int free;
    // 字节数组,用于保存字符串
    char buf[];
};

img

free:还剩多少空间 len:字符串长度 buf:存放的字符数组

(2)SDS与C字符串的区别

  • 常数复杂度获取字符串长度

    C字符串不记录字符串长度,获取长度必须遍历整个字符串,复杂度为O(N);而SDS结构中本身就有记录字符串长度的len属性,所有复杂度为O(1)。Redis将获取字符串长度所需的复杂度从O(N)降到了O(1),确保获取字符串长度的工作不会成为Redis的性能瓶颈

  • 杜绝缓冲区溢出

    C字符串不记录自身的长度,每次增长或缩短一个字符串,都要对底层的字符数组进行一次内存重分配操作。如果是拼接append操作之前没有通过内存重分配来扩展底层数据的空间大小,就会产生缓存区溢出;如果是截断trim操作之后没有通过内存重分配来释放不再使用的空间,就会产生内存泄漏

    而SDS通过未使用空间解除了字符串长度和底层数据长度的关联,3.0版本是用free属性记录未使用空间,3.2版本则是alloc属性记录总的分配字节数量。通过未使用空间,SDS实现了空间预分配惰性空间释放两种优化的空间分配策略,解决了字符串拼接和截取的空间问题

  • 减少修改字符串时带来的内存重分配次数

    (1)空间预分配

    ​ 为减少修改字符串带来的内存重分配次数,SDS采用了“一次管够”的策略:

    • 若修改之后SDS长度小于1MB,则多分配现有len属性相同大小的未使用空间,这时SDS的len属性的值将和free属性的值相同
    • 若修改之后SDS长度大于等于1MB,则扩充除了满足修改之后的长度外,程序会分配1MB的未使用空间

    (2)惰性空间释放

    ​ 为避免缩短字符串时候的内存重分配操作,SDS在数据减少时,并不立刻释放空间。而是将其作为未使用空间保留在SDS中,如果将来要对SDS进行增长操作的话,这未使用的空间就派上用场。——当然,SDS也有API用于释放未使用的空间。

  • 二进制安全

    C字符串中的字符必须符合某种编码,除了字符串的末尾,字符串里面是不能包含空字符的,否则会被认为是字符串结尾,这些限制了C字符串只能保存文本数据,而不能保存像图片这样的二进制数据

    而SDS的API都会以处理二进制的方式来处理存放在**buf属性的字符数组**里的数据,不会对里面的数据做任何的限制。SDS使用len属性的值来判断字符串是否结束,而不是空字符

  • 兼容部分C字符串函数

    虽然SDS的API是二进制安全的,但还是像C字符串一样以空字符结尾,目的是为了让保存文本数据的SDS可以重用一部分C字符串的函数

(3)总结:

比起C字符串,SDS具有以下优点:

  1. 常数复杂度获取字符串长度
  2. 杜绝缓冲区溢出
  3. 减少修改字符串长度时所需的内存重分配次数
  4. 二进制兼容
  5. 兼容部分C字符串函数

C字符串与SDS对比:

C字符串SDS
获取字符串长度复杂度为O(N)获取字符串长度复杂度为O(1)
API是不安全的,可能会造成缓冲区溢出API是安全的,不会造成缓冲区溢出
修改字符串长度必然会需要执行内存重分配修改字符串长度N次最多会需要执行N次内存重分配
只能保存文本数据可以保存文本或二进制数据
可以使用所有<string.h>库中的函数可以使用一部分<string.h>库中的函数

参考:《Redis设计与实现》

posted @ 2021-04-26 23:29  your_棒棒糖  阅读(56)  评论(0编辑  收藏  举报