摘自:https://juejin.cn/post/6844903936520880135
仅做个人备份,浏览请看原文
Redis是用C语言写的,但是Redis并没有使用C的字符串表示(C是字符串是以\0
空字符结尾的字符数组),而是自己构建了一种简单动态字符串(simple dynamic string,SDS)的抽象类型,并作为Redis的默认字符串表示。
源码结构体的组成
len
:记录当前已使用的字节数(不包括'\0'
),获取SDS长度的复杂度为O(1)alloc
:记录当前字节数组总共分配的字节数量(不包括'\0'
)flags
:标记当前字节数组的属性,是sdshdr8
还是sdshdr16
等,flags值的定义可以看下面代码buf
:字节数组,用于保存字符串,包括结尾空白字符'\0'
SDS与C字符串的区别
1. 常数复杂度获取字符串长度
C字符串不记录字符串长度,获取长度必须遍历整个字符串,复杂度为O(N);而SDS结构中本身就有记录字符串长度的len
属性,所有复杂度为O(1)。Redis将获取字符串长度所需的复杂度从O(N)降到了O(1),确保获取字符串长度的工作不会成为Redis的性能瓶颈
2. 杜绝缓冲区溢出,减少修改字符串时带来的内存重分配次数
C字符串不记录自身的长度,每次增长或缩短一个字符串,都要对底层的字符数组进行一次内存重分配操作。如果是拼接append操作之前没有通过内存重分配来扩展底层数据的空间大小,就会产生缓存区溢出;如果是截断trim操作之后没有通过内存重分配来释放不再使用的空间,就会产生内存泄漏
而SDS通过未使用空间解除了字符串长度和底层数据长度的关联,3.0版本是用free
属性记录未使用空间,3.2版本则是alloc
属性记录总的分配字节数量。通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化的空间分配策略,解决了字符串拼接和截取的空间问题
3. 二进制安全
C字符串中的字符必须符合某种编码,除了字符串的末尾,字符串里面是不能包含空字符的,否则会被认为是字符串结尾,这些限制了C字符串只能保存文本数据,而不能保存像图片这样的二进制数据
而SDS的API都会以处理二进制的方式来处理存放在buf
数组里的数据,不会对里面的数据做任何的限制。SDS使用len
属性的值来判断字符串是否结束,而不是空字符
4. 兼容部分C字符串函数
虽然SDS的API是二进制安全的,但还是像C字符串一样以空字符结尾,目的是为了让保存文本数据的SDS可以重用一部分C字符串的函数
总结:C字符串与SDS对比
C字符串 | SDS |
---|---|
获取字符串长度复杂度为O(N) | 获取字符串长度复杂度为O(1) |
API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
修改字符串长度必然会需要执行内存重分配 | 修改字符串长度N次最多会需要执行N次内存重分配 |
只能保存文本数据 | 可以保存文本或二进制数据 |
可以使用所有<string.h> 库中的函数 |
可以使用一部分<string.h> 库中的函数 |