底层实现-SDS 简单动态字符串

一 介绍

  • Redis只会使用C字符串作为字面量,大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态字符串)作为字符串表示。
  • 也就是说,Redis中字符串的底层实现,一般就是SDS。
  • 底层依赖于C的标准类型 - 数组。

SDS与C字符串的不同

  • 获取字符串长度更容易
        SDS加了元数据len与free,获取字符串长度的时间复杂度是O(1)。获取C字符串需要遍历整个字符串,直到遇到'\0'为止,所以时间复杂度是O(n)

  • 杜绝缓冲区溢出
        因为C字符串不记录自身的len与free,做strcat字符串合并这样的动作时,如果src本身空间不足以容纳dst,那么就就溢出并影响到紧接着的内存,使得后面的内存值被修改。SDS因为记录了len与free,做合并之前会先看空间是否满足,不够的话会先进行扩展,然后才执行拼接操作。

  • 减少修改字符串时带来的内存重分配次数
        C字符串的增减都需要进行一次内存重分配的操作,如append追加,必须先执行内存重分配来拓展底层数组的空间大小,如果忘了这一步,会产生缓冲区溢出。 如果执行trim截断,那么在执行这个操作后,程序需要通过内存重分配来释放字符串不再使用的空间,否则会发生内存泄露。(内存泄露:申请了的空间实际未必都用上,这部分也无法被OS回收,长此以往,会使机器内存资源耗尽)。内存重分配要通过系统调用,对于Redis这种数据频繁修改的程序,频繁的系统调用会消耗掉很大部分性能。所以SDS解除了字符串长度与底层数组的一一对应。在SDS中,buf数组的长度不一定就是字符数量加1,数组里面可以包含未使用的字节,而这些字节的数量由SDS的free属性记录。
  1.    空间预分配
        用于优化SDS的字符串增长操作,连续增长N次字符串所需的内存重分配次数,从N次降低为最多N次。如果对SDS进行修改后,SDS长度(len)少于1MB,那么把free字段也设置成len字段的值。也就是说修改后SDS使用率是50%。如果大于等于1MB,那么FREE就不会那么激进去double一下len,程序会分为1MB的未使用空间,free是1MB。此时的使用率是小于等于50%的。通过预分配策略,减少增长字符串时所做的内存重分配带来的系统调用开销。要拓展SDS字符串时,会先看SDS的free空间是否足够容纳新的字符,如果能,就不会去拓展SDS的空间了。
  1.     惰性空间释放
        用于优化SDS的字符串缩短操作。当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些自己的数量记录起来,并等待将来使用。当然,SDS也有相应的API,让我们可以在有需要时,真正地释放SDS的未使用空间,所以不用担心浪费。

  • 二进制安全
        SDS的buf是字节数组,保存的是二进制数据,而不是字符。C语言的字符串,除了末尾,中间不能出现'\0'字符,否则会被误会成是字符串结尾。万一有些图片、音频、视频、压缩文件数据,中间有多个'\0',这样C一读到就以为结束了,这样是不行的,这些限制使得C字符串只能保存文本数据。SDS的API是二进制安全的,数据写入时是怎么样,读出就是怎么样,保存怎么样的数据都可以。SDS可以做到这样,主要是因为SDS是根据len来判断字符串是否到了末尾,而不是根据'\0'来判断。

  • 兼容部分C字符串函数
        为了可以重用一部分C标准函数,SDS的API在保存数据时,以为在末尾设置'\0'字符,并且在为buf分配空间的时候多分配一个字节来容纳空字符。例如使用的strcasecmp(sds->buf,'hello world')与strcat(c_string,sds->buf),就不用专门另外编写一个实现这些功能的函数了。

总的来说,就是字符串加了len与free这两个元数据,使得实现一些算法更容易。

C字符串 与 SDS 的区别

源码
sds.h
sds.c


二  数据结构

struct sdshdr {
    unsigned int len;    // buf中已占用的长度,不包括空字符
    unsigned int free;  // buffer中剩余可用长度
    char buf[];             // 实际保存字符串数据的数组
};




三  sds主要的api



主要是对sds、sds里面数组内容的增删查改。





你可能感兴趣的:(Redis)