一. 基本使用
redis> set msg "hello world"
OK
二. SDS的定义
SDS遵循了C语言字符串的以空字符结尾的管理 保存空字符的一字节空间是不计算在len里面的并且为空字符分配额外的1字节空间以及添加空字符到字符串的末尾等操作都是由SDS函数自动完成。 其目的是 可以复用一些C语言的库函数。
下面给大家展示一个对应的String类型的数据:
三. SDS 与 c字符串的区别
3.1 常数复杂度获取字符串的长度
因为C语言是不会记录一个字符串的长度的 所以为了获取一个C字符串的长度需要遍历整个字符串 对遇到的每个字符进行计数 直到遇到代表字符串结束的字符为止 这个操作的时间复杂度为O(N)而根据我们上面所说的 redis的SDS结构存储的字符串有一个len属性 这个属性记录了当前字符串的长度并且是不包含'\0'的所以我们可以以O(1)的时间复杂度获取字符串的长度。
3.2 杜绝缓存区移出
除了获取长度的时间复杂度高之外 C字符串不记录自身的长度还会带来一个问题就是容易造成缓冲区的溢出 例如C语言中的 *strcat 命令可以将一个字符串的内容拼接到另一个字符串的后面但是前提是 C语言的程序再执行这个代码的时候假设程序已经分配了足够的缓冲给到这个字符串于是开始进行拼接但是如果忘记分配缓冲区的话 就会造成缓冲区的溢出
下面举个栗子: 假设我们有两个在内存中紧挨着的字符串 redis 和 mongdoDB 这个时候我们执行 *strcat(s1," Sentinel") 我们执行这个函数将redis改为 redis sentinel 但是这个时候 这个时候如果我们没有为S1提前分配足够的空间那么就会造成后面的字符串被覆盖
而当我们使用SDS数据结构进行字符串的修改的时候 首先会判断当前字符串分配的大小是不是满足修改后的字符串的大小 如果不满足会进行扩容然后再进行操作 这也就避免了 出现缓冲区溢出
3.3 减少修改字符串时带来的内存重分配次数
当我们在使用字符串进行操作的时候 我们总需要进行内存的重新分配 例如 当我们进行字符串的拼接操作的时候 如果当前的缓存区小于字符串的长度 需要进行扩容 再比如当我们进行字符串的截断的时候需要回收多余的内存 但是这些操作五无疑是非常耗时的 作为内存数据库的redis是无法容忍的 所以 redis 主要从以下两个方面进行 SDS的优化减少内存重新分配的次数
3.3.1 空间预分配
额外分配的未使用空间数量得到公式主要由以下公式决定:
- 如果对SDS修改之后SDS的长度小于1MB那么程序分配和len同样大小的未使用空间 这时 SDSlen属性的值将和free属性的值相同 也就是说这个时候程序会分配两倍空间 一份存储当前值 一份进行备用 当然还有多余的一字节存储空字符
- 如果当SDS修改之后的长度大于等于1MB那么程序会分配1MB未使用空间 举个例子 如果进行修改好之后 长度大于1MB 那么程序将会分配1MB的未使用空间供后续的时候 也就是说当我们修改后的字符串为30MB 那么 SDS的buf数组的实际长度为 30MB+1MB+1byte
3.3.2 惰性空间回收
除了上面所说的对于字符串的append操作造成的空间分配的问题同样当字符串在进行截断操作的时候 redis是如何进行内存的回收的呢?
当我们使用sds进行trim的操作的时候 他并不会 立即释放出当前字符数组中的空闲位置而是采取了一种惰性空间回收的机制 如果以后再次进行append的操作可以不用再去申请内存了
但是 也提供了我们API来进行这部分空间的释放 在需要的时候
3.4 二进制安全
因为C语言是用空字符进行字符串结束的一个标志位 所以他也就无法进行图片 音频 视频 等 甚至是你程序中 用空字符去截断的地方都无法使用 这也是无法忍受的地方 但是经过了我们介绍完 redis的存储结构我们可以看到 redis虽然也是空字符结尾但是redis只是为了可以复用C语言的一些函数 但是并没有 使用空字符进行字符串的结束的一个标志 所以SDS是二进制安全的