深入理解Redis—简单动态字符串(SDS)

发布于:2023-01-22 ⋅ 阅读:(442) ⋅ 点赞:(0)

简单动态字符串(SDS)

      Redis没有使用传统C字符串(以空字符串结尾),而是自己构建了一种名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示;

      命令:SET  msg  “hello  world”

    服务器执行以上命令将会在底层创建一个新的键值对:是一个字符串对象,底层实现是一个保存了字符串“msg”的SDS;也是一个字符串对象,对象底层是一个保存了“hello  world”的SDS;

SDS的定义

      SDS由sds.h/sdshdr结构表示:

SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,且为空字符额外分配1字节的空间;

      使用空字符串结尾的好处:SDS可以直接重用一部分C字符串函数库内的函数;

     

SDS与C字符串的区别

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

      C字符串不记录自身长度信息,则获取字符串长度时间复杂度为O(N)

      SDS底层结构中使用len属性记录了自身长度,则获取长度的时间复杂度为O(1)

      意义:使用SDS而非C字符串,Redis将获取字符串长度的时间复杂度由O(N)降低到O(1),确保了获取字符串长度的工作不会成为Redis的性能瓶颈

     

杜绝缓冲区溢出

      C字符串除获取长度信息时间复杂度高之外,还容易造成缓冲区溢出;

      当执行char *strcat(s1 , s2)拼接字符串的操作时,若不事先考虑s1串底层字符数组的容量能否容下拼接后串s1+s2的长度的问题,就容易造成缓冲区溢出的情况;

      SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:

  • 当SDS的API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,若不满足,API会自动先将SDS的空间扩展至执行修改操作所需的大小,在执行实际的修改操作,即完全避免了缓冲区溢出的可能性;

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

      C字符串底层使用N+1的字符数组来保存长度为N的字符串,因此每次需要增长字符串前为防止内存溢出,需要先进行底层的数组扩展;再缩短字符串前,为防止内存泄漏,需要先进行内存收缩;

      内存重分配涉及复杂的算法,且可能需要执行系统调用,因此不论扩展或收缩都是一个较为耗时的操作;

      Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,因此若频繁地执行内存重分配,就可能会对性能造成影响;

  1. 空间预分配

用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,且需要对SDS进行空间扩展时,程序不仅会为SDS分配修改所必须的空间,还会为SDS分配额外的未使用空间;

额外分配的未使用空间公式:

    1. 若对SDS进行修改后,SDS的长度小于1MB,程序分配和len属性等大的未使用空间,即此时的SDS的len属性的值与free属性值相同;
    2. 若对SDS进行修改后,SDS的长度大于等于1MB,则程序会分配1MB未使用空间;

通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数;

当再次扩展SDS空间的时候,程序就会检查free属性(即未使用空间)是否足够,若足够,则直接使用未使用的空间,而无需执行内存重分配操作;

意义:通过空间预分配策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次,降低为最多N次

  1. 惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出的字节,而是使用free属性将这些字节的数量记录起来,等待使用;

通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配操作,并为将来可能有的增长操作提供了优化;

二进制安全

      C字符串底层为字符数组,并以空字符‘\0’为结束符,因此只能用于保存文本数据;同时,若碰到一些特殊格式的字符串,如以空字符为分隔符,则就会被C字符串误认为结束符;

      为确保Redis可以适用于各种不同的使用场景(保存除了文本数据之外的二进制数据,如音频、图片、视频等),SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式处理SDS存放在buf数组里的数据;

      SDS底层的buf数组为字节数组,用于保存所有二进制数据,而不局限于文本数据;

兼容部分C字符串函数

      虽然SDS的API都是二进制安全的,但一样遵循C字符串以空字符结尾的惯例,即API总会将SDS保存的数据末尾设置为空字符,且总会在buf数组分配空间时多分配一个字节来容纳该空字符,目的是为了当保存的数据是文本数据时,SDS能够重用一部分<string.h>库定义的函数;

 

本文含有隐藏内容,请 开通VIP 后查看