带你一起认识Redis的简单动态字符串

119 阅读3分钟

Redis中所有包含键值对底层都是由SDS实现的。

redis并没有直接使用C语言的传统字符串,而是以自己构建了一种名称为简单动态字符串的的抽象类型(Simple Dynamic string, SDS)的抽象类型,并将SDS作为redis的默认字符串类型。

set firtKey "hello world"

键值对的键 "firstKey" 使用的是 SDS,键值对的值 “hello world” 也是使用的SDS

RPUSH animals "dog" "fish" "people" "cat"

键值对的键 “animals”是使用的SDS

键值对的值是在列表 animals后面插入四个字符串,这四个字符串用的就是SDS.

SDS简单动态字符串在redis中的定义

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

举例如下

clipboard.png

  • free为5表示这个字符串还有5个空余空间可以使用。
  • len为5表示这个SDS保存了5个字节的字符串。
  • buf是一个cha类型的数组,本次保存了5个字符,最后一个字符‘\0’是,是字符串结束符空字符,SDS遵循C语言以空字符来结束字符串。

这样定义的好处是什么呢?

1.常数复杂度获取字符串长度

如果想要获取SDS的字符串长度,直接读取len的值就可以直接获取到,时间复杂度为O(1),而C语言获取字符串的长度是O(n),对于长字符串来说节省的时间不是一点点。

2.空间预分配

redis 在给字符串分配空间的时候,如果字符串长度小于1M会按照实际字符串长度2倍的大小去预分配内存空间,如果字符串大于1M每次扩容的时候只会多分配1M的存储空间。这么做的原因每次增长或者缩短一个 C 字符串, 程序都总要对保存这个 C 字符串的数组进行一次内存重分配操作。因为内存重分配涉及复杂的算法, 并且可能需要执行系统调用, 所以它通常是一个比较耗时的操作,对于快速的redis来说会有一定的性能影响。如果redis对SDS进行N修改的话,分配内存的次数由N次变为了最多N次。

3.惰性空间释放

惰性空间释放的意思是,在SDS的API要缩短字符串长度的时候,并不会像C语言那样重新分配内存空间释放掉多余的空间,而是把释放出来的空间通过free把长度记录起来。等待将来再次使用。惰性释放内存空间避免了内存重新分配,缩短了一定的执行时间,提高了性能,并且为将来可能会增长的字符串预留空间,当然也会带来另一个问题,就是大量的内存空间被空置。

4.二进制安全

C语言中的字符串必须符合某种编码(比如ASCII),并且除了字符串末尾外,字符串不能包含空字符\0,否则会被认为是字符串的结尾。这些限制就导致C只能保存文本数据,而不能保存图像等二进制数据。而SDS都是二进制安全的binary-safe,所有SDS API都会以二进制的方式处理存放在SDS中的数据。程序不会对其中的数据做任何限制、过滤或者假设。数据被写入时候是什么样的,读取出来后就是什么样的。

5.兼容部分C语言的字符串函数

因为Redis的源码是C语言写的,所以SDS可以部分重用<string.h>里面的函数。