简单动态字符串
什么是SDS?
Redis没有直接使用C语言的字符串,而是自己创建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型, 以此来表示Redis中的字符串。
SDS的数据结构?
struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
SDS使用len属性的值而不是空字符来判断字符串是否结束。
为什么Redis要使用SDS而不是C字符串?
1.常数复杂度获取字符串长度。
SDS记录了所存字符串的长度,而C获取字符串长度需要遍历整个字符串,前者将复杂度从O(N)降到了O(1)。
2.杜绝缓存区溢出。
C字符串在拼接字符串时不会先检查内存的空间是否足够,而SDS内部的字符串拼接函数在拼接字符串前会检查空间是否足够,如果不够,则先扩展SDS空间,然后再执行拼接操作。
避免频繁地分配空间,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关系,实现了空间预分配和惰性空间释放两种优化策略。
- 空间预分配:在对SDS进行空间扩展的时候,程度不仅会为SDS分配修改所需要的必须空间,还会分配额外的未使用空间。具体分配策略:len大小小于1M时,分配相同大小的free空间,此时len和free属性值相同;当len大于等于1M时,分配1M未使用空间。
- 惰性空间释放:用于SDS字符串缩短操作。当需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,未来使用。
3.二进制安全。
C字符串的除了末尾外,不能包含空字符,否则会被认为是字符串结尾,这限制了C字符串只能保存文本数据,不能保存二进制数据。而SDS API会以处理二进制的方式来处理SDS存放在buf数组里的数据。
4.兼容部分C字符串函数
为了是能够让SDS可以重用<string.h>库定义的函数。
未使用空间在SDS中的作用?
SDS在字符数组buf的字符串末尾分配了额外的1字节空间,这样做的好处是SDS可以直接重用一部分C字符串函数库。但是不会算作字符串的长度
链表
Redis的链表的数据结构:
typedef struct list {
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//链表所包含的节点数量
unsigned long len;
//节点值复制函数
void *(*dup) (void *ptr);
//节点值释放函数
void (*free) (void *ptr);
//节点值对比函数
int (*match) (void *ptr, void *key);
} list;
- dup函数用于复制链表节点所保存的值
- free函数用于释放链表节点所保存的值
- match函数则用于对比链表节点所保存的值和另一个输入值是否相等
其中,链表的节点是listNode类型
typedef struct listNode {
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点的值
void *value;
} listNode;
- 每个节点都有指向一个前置节点和后置节点,因此Redis的链表是个双端链表
- 链表表头节点的前置节点和表尾节点的后置节点都指向NULL,所以Redis链表是个无环链表。
- 链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。具体是如何办到的?
字典
Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存了字典中的一个键值对。
Redis中的字典定义:
typedef struct dict {
//类型特定函数
dictType * type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引,当rehash不在进行时,值为-1
int trehashidx;
} dict;
- type属性和 privdata属性针对不同类型的键值对,为创建多态字典而设置的
- type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
- privdata属性保存了需要传给那些特定类型函数的可选参数。
- 字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
- rehashidx记录了rehash的进度,如果目前没有在进行rehash,那么它的值为-1.
其中,哈希表定义:
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值,总是等于size-1
unsigned long sizemask;
//该哈希表已有节点的数量
unsigned long used;
} dictht;
哈希表节点定义:
typedef struct dictEntry {
//键
void *key;
//值
union {
void *val;
uint64_tu64;
int64_ts64;
} v;
struct dictEntry *next;
} dictEntry;
- key保存键值对中对键
- v保存键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整数,或者是一个int64_t整数。
- next属性指向另一个哈希表节点的指针,用来解决键冲突