一、简单动态字符串
- Redis没有直接使用C语言传统的字符串表示(以空字符串结尾的字符数组),构建了一种名为简单动态字符串的抽象模型。但是也存在C语言的字符串
- 字符串的使用方式
- C字符串:用在无须对字符串值进行的字符串字面量
- SDS:可以被修改的字符串,在Redis的数据库里面,包含字符串值的键值对在底层都是SDS实现的。
1 SDS
1.1 SDS结构
- SDS结构
- 代码
struct sdshdr {
//记录buf数组中已经使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
- 结构图:长度为5 个字节长的字符串
- 注意点:
- SDS遵循了C语言的语法,最后一位使用空字符,但是不记录到len中
1.2 SDS设计优势
- O(1)时间复杂度获取字符串长度len
- 杜绝了缓冲区溢出,在插入数据的时候,会判断free是否够用,如果不够则重新分配空间。
- 减少修改字符串时带来的内存重分配次数。
- 在内部不够的情况下,不会出现由于内部不足,导致的内存溢出
- 在内部多余的情况下,不会出现由于内存太多空余,导致的内存泄漏
- SDS使用空间预分配和惰性空间来优化空间不足时分配空间和空间足够时,内存泄漏的问题
- 空间预分配
- 用于优化SDS的字符串增长操作,当对SDS进行空间扩展时,不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。
- 规则:
- len < 1MB:程序会分配和len属性同样大小的未使用空间,这时SDS len和free的值相等。
- len >= 1MB:程序分配1MB的未使用空间
- 惰性空间释放
- 用于优化SDS的字符串缩短操作。在缩短的时候,程序并不会立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。同时,SDS中在有需要的时候,真正释放SDS中未使用空间,不用担心内存泄漏。
- 空间预分配
二、链表
1 链表与链表节点实现
1.1 链表与链表节点结构
-
结构
-
链表节点
typedef struct listNode{ //前置节点 struct listNode * prev; //后置节点 struct listNode * next; //节点的值 void * value; }
-
链表节点包含着前驱和后继,链表是双向链表构成。
-
链表
typedef struct list{ //表头节点 listNode * head; //表尾节点 listNode * tail; //链表所包含的节点数量 unsigned long len; //节点值复制函数,用于复制链表节点所保存的值 void *(*dup) (void *ptr) //节点释放函数,用于释放链表节点所保存的值 void (*free) (void* ptr) //节点值对比函数,用于对比链表节点所保存的值和另一个输入值是否相等 int (*match) (void* ptr,void* key) }
-
链表图示
-
特性
- 获取头节点和尾节点的时间复杂度为O(1)
- 无环:头结点的prev指针和尾节点的next指针都是null,对链表的访问以null为终点
- 获取链表长度的时间复杂度为O(1)
- 多态:链表节点使用void*指针来保存节点值,并且可通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。