reids:动态字符串|青训营笔记

47 阅读4分钟

一:动态字符串

Redis构架了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,作为自己的默认字符串.除了保存数据之外,还可用作缓冲区:AOF模块中的AOF缓冲区,输入缓冲区。

SDS的定义

sruct sdshdr {
	//记录buff数组中已使用的字节制度程度,不包括'\0',即字符串程度
	int len;
	//记录未使用字节数量
	int free;
	//字节数组
	char buf[];
}
  • 遵循C字符串空字符结尾惯例,'\0'的一字节空间不在计算范围内。
  • 直接重用了一部分C字符串的函数,例如打印函数(printf)

SDS的特点

  • C字符串:
    • 使用长度为N+1的字符数组,以'\0'结尾,不能满足Redis对字符串安全性,效率以及功能方面的要求,字符串长度与底层字符数组空间相关联
    • 本身不记录自身的长度,获取自身长度需要遍历整个字符数组,时间复杂度O(n)。
    • 不记录自身长度还会导致容易导致缓冲区溢出Cstring缓冲区溢出.png
      • 若在djccc后面插入字符串,会默认空间足够,导致覆盖别的字符串对象
    • 内存重分配次数比较多,每次增长或缩短买都会进行一次内存重分配操作。
    • 二进制不安全
  • SDS:
    • 常数复杂度获取字符串长度
      • 因为结构体内有一个len属性记录了SDS的长度,每次字符串修改后都会自动修改len,这样可以O(1)复杂度获取字符串长度。
    • 杜绝缓冲区溢出
      • 结构体中有一个len和free属性,当需要修改字符串对象时,会让len与free相加,与要修改的字符串长度比较,若足够则在原本基础上修改,否者重新开辟一段内存空间,拓展对象空间。(包括空间预分配)
    • 减少修改字符串时带来的内存重分配次数
      • 增长或缩短都可能会导致内存重分配操作:
        • 情景:
          • 若增长后的长度大于底层字节数组空间,需要内存重分配
          • 若缩短后不再使用那部分空间,由于没有修改数组,空间会一直占用,会产生内存泄漏
          • 若一般情况下不太经常修改程度可以介绍,但Redis频繁修改,所以要减少内存空间重分配的次数。
          • 有空间与分配和惰性空间释放两种策略。
        • 空间预分配
          • 拓展SDS空间之前会判断未使用空间(free)是否足够,若足够,则不需要拓展。
          • 对SDS进行空间拓展的时候,不进会分配所修改必须要的空间,还会分配未使用的空间。
          • 若SDS修改后的长度(即len)将小于1MB,那么将分配与len等长的空间,即len=free,若大于等于1MB,会分配1MB未使用空间,即free=1MB。
        • 惰性空间释放
          • 缩短空间时,不会立即通过内存重分配回收未使用空间,而是会使用free来记录下来,以便在以后增长的时候利用,减少增长时导致的内存重分配,以及本次缩短导致的内存重分配。
          • 通时,SDS提供了相应的API,可以在有需要的时候释放SDS未使用的·空间,不需要担心惰性空间释放策略造成的内存浪费。
          • 另外可能会导致内存碎片问题,当SDS的未使用空间散布在SDS字符串的各个位置时,可能会出现无法分配连续内存块的情况,从而导致内存碎片。为了解决这个问题,Redis提供了内存碎片整理(memory defragmentation)功能,可以将SDS字符串的内容移动到连续的内存块中,从而消除内存碎片。但是,内存碎片整理需要消耗额外的时间和资源,因此需要在必要时才进行。
    • 二进制安全
      • buf是字节数组而不是字符数组,是二进制安全的。C的字符串字符数组必须符合某种二进制编码,只能保存文本文件。
      • 不是用这个数组来保存字符的,而是来保存一系列二进制数据,所以保存'\0'等一些特殊字符都没关系,因为他是通过len而不是'\0'来判读是否结束的,字节数组也可以保存一些特殊的数据如音频,视频等,不会对其中的数据做任何限制、过滤、或者假设,数据写入是什么样的,读取就是什么样的。
    • 兼容部分C字符串函数
      • 他在结尾用'\0'是为了保存文本数据的那些SDS可以重用一部分<string.h>库定义的函数,例如对比函数(<string.h>/strcasecmp)、追加函数(将SD作为地个人股参数追加到C字符串后面。