字符串
作为内存数据库,Redis对字符串的底层数据结构做了很多优化,通过了解Redis的数据结构,我对Redis的高性能有了更深的理解。
Redis中的字符串
Redis中的字符串有两种表现形式:
- C语言传统字符串(以空字符结尾的字符数组) :适用于无需对字符串值进行修改的地方(eg:打印日志)
- SDS: 可以被修改的字符串(eg:大部分场景)
SDS简介
class SDS {
// SDS中已使用字节的数量 = SDS保存的字符串的长度
int len;
// 记录SDS中未使用字节的数量
int free;
// 保存的字符串
char[] buf;
}
- len:已使用空间大小(字符串的长度)
- free: 未使用空间大小
- buf: 字符串内容
图示:
SDS VS C
相比于C类字符串,SDS具有很多C字符串不具备的特点。
-
获取字符串的长度
C字符串: 由于没有记录自身的长度,所以要遍历整个字符串,O(n)
SDS: 获取len属性O(1) -
杜绝缓冲区溢出
C类字符串(会溢出)
①: s1 =“Redis”; s2=“MongoDB”;
②:s1 =“Redis Cluster”;
③:缓存区溢出,此时的s2由原来的MongoDB变成了Cluster,原因:s1没有预分配冗余空间。SDS(不会溢出)
①:查看剩余空间(free)是否足够
②:如果足够,则直接使用预分配的空间;如果不够,则先扩容(同时预分配冗余空间),再拼接。
-
减少字符串修改带来的内存重分配次数
C字符串
因为C字符串的长度和底层数组长度之前存在着关联性(底层数组长度=C字符串长度+1,额外的一空间用于保存空字符串),所以每次增长、缩短一个字符串,程序总要对这个字符串进行内存重分配操作;但是内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以比较耗时。
SDS (空间预分配+惰性释放)
①:空间预分配 (扩容策略)
当SDS的剩余空间不足需要进行扩容时,不仅会分配扩容需要的必要空间,还会分配额外的未使用空间。
额外分配的空间: 如果修改后,SDS的长度(len属性)< 1M,则分配和len一样大的额外空间(free =len),此时数组buf的实际长度为:2 X len + 1;
②:惰性空间释放 (缩容策略)
当SDS需要缩容时,程序并不会立即回收多出来的空间,而是使用free属性将多余的空间记录下来,等待将来使用。
-
二进制安全
C字符串
C字符串必须符合特定的编码(eg:ASCII),并且除了字符串末尾外,字符串里面不能包含空字符(否则最先读入的空字符会被认为是字符串结尾),这就限制C字符只能保存文本数据,不能保存图片、音频、视频、压缩文件等二进制数据。
SDS
SDS在处理二进制数据时,不是通过空字符来判断字符串是否结束,而是通过len属性。所以SDS不仅可以保存文本数据,还可以保存任意格式的二进制数据。 -
兼容部分C字符串函数
虽然SDS是二进制安全的,但是还是遵循了C字符以空字符结尾的习惯,这样就可以使用部分C函数,避免了重复开发。
总结
相比于C字符串,Redis的SDS具有以下的特点:
- 常数级的获取字符串长度
- 杜绝了缓冲区溢出
- 减少了字符串修改带来的内存重分配次数(注意:不是避免)
- 二进制安全(这就意味着我们可以将图片、视频等内容保存在Redis中,我们通常只用String保存字符串)