标签:空间 避免 ddl 类型 数据 扩展 释放空间 额外 操作
redis的字符串不是直接用c语言的字符串,而是用了一种称为简单动态字符串(SDS)的抽象类型,并将其作为默认字符串。
1 /* 2 * 保存字符串对象的结构 3 */ 4 struct sdshdr { 5 6 // buf 中已占用空间的长度 7 int len; 8 9 // buf 中剩余可用空间的长度 10 int free; 11 12 // 数据空间 13 char buf[]; 14 };
SDS遵循C字符串以空字符结尾的惯例,但是那1个字节不计算在len中。
可以重用C字符串库函数里的函数。
1、常数复杂度获取字符串长度
C语言如果要获取字符串的长度,需要从第一个字符开始,遍历整个字符串,直到遍历到\0符号,时间复杂度是O(N),即字符串的长度。
而redis由于已经存储了字符串的长度,因此,时间复杂度是O(1)。
这样,避免了获取大字符串长度时时间的缓慢。
2、杜绝缓冲区溢出
C语言给字符串开辟一个存储空间,如果对此存储空间的使用超过开辟的空间,会导致内存溢出。
例如使用字符串拼接等方式时,就很容易出现此问题。而如果每次拼接之前都要计算每个字符串的长度,时间上又要耗费很久。
redis的SDS中内置一个sdscat函数,也是用于字符串的拼接。但是在执行操作之前,其会先检查空间是否足够。
如果free的值不够,会再申请内存空间,避免溢出。
3、减少内存分配次数
C语言的字符串长度和底层数组之间存在关联,因此字符串长度增加时需要再分配存储空间,避免溢出;字符串长度减少时,需要释放存储空间,避免内存泄漏。
redis的sds,主要是通过free字段,来进行判断。通过未使用空间大小,实现了空间预分配和惰性空间释放。
1)空间预分配
当需要增长字符串时,sds不仅会分配足够的空间用于增长,还会预分配未使用空间。
分配的规则是,如果增长字符串后,新的字符串比1MB小,则额外申请字符串当前所占空间的大小作为free值;如果增长后,字符串长度超过1MB,则额外申请1MB大小。
上述机制,避免了redis字符串增长情况下频繁申请空间的情况。每次字符串增长之前,sds会先检查空间是否足够,如果足够则直接使用预分配的空间,否则按照上述机制申请使用空间。
1 /* 2 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后, 3 * buf 至少会有 addlen + 1 长度的空余空间 4 * (额外的 1 字节是为 \0 准备的) 5 * 6 * 返回值 7 * sds :扩展成功返回扩展后的 sds 8 * 扩展失败返回 NULL 9 * 10 * 复杂度 11 * T = O(N) 12 */ 13 sds sdsMakeRoomFor(sds s, size_t addlen) { 14 15 struct sdshdr *sh, *newsh; 16 17 // 获取 s 目前的空余空间长度 18 size_t free = sdsavail(s); 19 20 size_t len, newlen; 21 22 // s 目前的空余空间已经足够,无须再进行扩展,直接返回 23 if (free >= addlen) return s; 24 25 // 获取 s 目前已占用空间的长度 26 len = sdslen(s); 27 sh = (void*) (s-(sizeof(struct sdshdr))); 28 29 // s 最少需要的长度 30 newlen = (len+addlen); 31 32 // 根据新长度,为 s 分配新空间所需的大小 33 if (newlen < SDS_MAX_PREALLOC) 34 // 如果新长度小于 SDS_MAX_PREALLOC 默认1M 35 // 那么为它分配两倍于所需长度的空间 36 newlen *= 2; 37 else 38 // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC 39 newlen += SDS_MAX_PREALLOC; 40 // T = O(N) 41 newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1); 42 43 // 内存不足,分配失败,返回 44 if (newsh == NULL) return NULL; 45 46 // 更新 sds 的空余长度 47 newsh->free = newlen - len; 48 49 // 返回 sds 50 return newsh->buf; 51 }
2)懒惰空间释放
懒惰空间释放用于优化sds字符串缩短的操作
当需要缩短sds的长度时,并不立即释放空间,而是使用free来保存剩余可用长度,并等待将来使用。
当有剩余空间,而有有增长字符串操作时,则又会调用空间预分配机制。
当redis内存空间不足时,会自动释放sds中未使用的空间,因此也不需要担心内存泄漏问题。
4、二进制安全
SDS 的 API 都是二进制安全的: 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据, 程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样。
sds考虑字符串长度,是通过len属性,而不是通过\0来判断。
5、兼容部分C语言字符串函数
redis兼容c语言对于字符串末尾采用\0进行处理,这样使得其可以复用部分c语言字符串函数的代码,实现代码的精简性。
标签:空间 避免 ddl 类型 数据 扩展 释放空间 额外 操作
原文地址:https://www.cnblogs.com/mengchunchen/p/9025139.html