一.SDS的简单介绍
SDS:简单动态字符串(simple dynamic string)
2)SDS用来保存数据库中的字符串值
3)SDS被用作缓冲区:比如AOF模块的AOF缓冲区,以及客户端状态中的输入缓冲区
二.SDS的结构
struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中剩余可用空间的长度 int free; // 字节数组 char buf[]; };
分析:
1)free=5:代表空闲空间长度为5
2)len=5:代表已经使用的空间长度为5
三.Redis使用SDS的原因
1)常数复杂度获取字符串长度:O(1)
C字符串获取字符串长度时间复杂度为O(N),使用SDS可以确保获取字符串长度的操作不会成为Redis的性能瓶颈
2)杜绝缓冲区溢出
C字符串不记录自身长度和空闲空间,容易造成缓冲区溢出,使用SDS则不会,SDS拼接字符串之前会先通过free字段检测剩余空间能否满足需求,不能满足需求的就会扩容
3)减少修改字符串时带来的内存重分配次数
使用C字符串的话:
每次对一个C字符串进行增长或缩短操作,长度都需要对这个C字符串数组进行一次内存重分配,比如C字符串的拼接,程序要先进行内存重分配来扩展字符串数组的大小,避免缓冲区溢出,又比如C字符串的缩短操作,程序需要通过内存重分配来释放不再使用的那部分空间,避免内存泄漏
使用SDS的话:
通过SDS的len属性和free属性可以实现两种内存分配的优化策略:空间预分配和惰性空间释放
1.针对内存分配的策略:空间预分配
在对SDS的空间进行扩展的时候,程序不仅会为SDS分配修改所必须的空间,还会为SDS分配额外的未使用的空间
这样可以减少连续执行字符串增长操作所需的内存重分配次数,通过这种预分配的策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次,这是个很大的性能提升!
2.针对内存释放的策略:惰性空间释放
在对SDS的字符串进行缩短操作的时候,程序并不会立刻使用内存重分配来回收缩短之后多出来的字节,而是使用free属性将这些字节的数量记录下来等待将来使用,通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配次数,并且为将来可能有的增长操作提供了优化!
4)二进制安全
为了确保数据库可以二进制数据(图片,视频等),SDS的API都是二进制安全的,所有的API都会以处理二进制的方式来处理存放在SDS的buf数组里面的数据,程序不会对其中的数据做任何的限制,过滤,数据存进去是什么样子,读出来就是什么样子,这也是buf数组叫做字节数组而不是叫字符数组的原因,以为它是用来保存一系列二进制数据的
通过二进制安全的SDS,Redis不仅可以保存文本数据,还可以保存任意格式是二进制数
四.SDS的主要API及其源码解析
1)sdsnew函数:创建一个包含给定字符串的SDS
sds sdsnew(const char *init)
/* * 根据给定字符串 init ,创建一个包含同样字符串的 sds * * 参数 * init :如果输入为 NULL ,那么创建一个空白 sds * 否则,新创建的 sds 中包含和 init 内容相同字符串 * * 返回值 * sds :创建成功返回 sdshdr 相对应的 sds * 创建失败返回 NULL * * 复杂度 * T = O(N) */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } /* * 根据给定的初始化字符串 init 和字符串长度 initlen * 创建一个新的 sds * * 参数 * init :初始化字符串指针 * initlen :初始化字符串的长度 * * 返回值 * sds :创建成功返回 sdshdr 相对应的 sds * 创建失败返回 NULL * * 复杂度 * T = O(N) */ sds sdsnewlen(const void *init, size_t initlen) { struct sdshdr *sh; // 根据是否有初始化内容,选择适当的内存分配方式 // T = O(N) if (init) { // zmalloc 不初始化所分配的内存 sh = zmalloc(sizeof(struct sdshdr) + initlen + 1); } else { // zcalloc 将分配的内存全部初始化为 0 sh = zcalloc(sizeof(struct sdshdr) + initlen + 1); } // 内存分配失败,返回 if (sh == NULL) return NULL; // 设置初始化长度 sh->len = initlen; // 新 sds 不预留任何空间 sh->free = 0; // 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中 // T = O(N) if (initlen && init) memcpy(sh->buf, init, initlen); // 以 结尾 sh->buf[initlen] = '