本文主要从redis的基本数据结构开始总结
一、简单动态字符串
1.1 结构描述以及示意图
struct sdshdr{
// 已使用长度
int len;
// 未使用长度
int free;
// 实际保存数据的地方
char buf[];
};
图示:
下图显示的是一个总长度为5,且已经存储了redis这个字符串的结构:
下图显示的是一个总长度为10,且已经存储了redis这个字符串的结构:
1.2 为什么redis要自定义SDS
原因以及优点总结如下:
- 获取字符串长度只要常数复杂度;
- 避免缓冲区溢出;
- 减少修改字符串带来的内存重新分配次数,SDS的解决方式是空间预分配和惰性释放;
- 二进制安全;
- 同时SDS也能够兼容大部分C字符串函数。
二、链表
链表作为一种常用的数据结构,内置在很多高级语言中,但C语言偏偏没有提供现成的链表(有知道原因的小伙伴可以评论区留言),因此redis只能自己实现一遍。
2.1 结构描述以及示意图
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
通过将listNode串联起来则可以形成一个双向链表:
但是redis定义了list这个结构,除了串联起对应的链表,还加入了一些额外的字段:
typedef struct list {
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 复制方法
void *(*dup)(void *ptr);
// 释放方法
void (*free)(void *ptr);
// 对比方法
void (*match)(void *ptr, void *key);
// 节点数量
unsigned long len;
} list;
2.2 链表特性
- 双端:获取前置节点与后置节点的复杂度都是O(1)
- 无环:链表的访问以NULL为终点
- 带表头指针和表尾指针:获取表头节点与表尾节点的复杂度为O(1)
- 带长度计数器:获取长度的复杂度为O(1)
- 多态:使用void * 指针保存节点值,并且三个方法dup,free,match都可以为节点值设置特定函数,因此链表能存储各种不同类型的值
三、字典
字典是一种用于保存键值对的抽象数据结构,键值对值得是一个键和一个值进行关联。
3.1 结构描述以及示意图
3.1.1 哈希表
typedef struct dictht {
// 哈希表数组
dictEntry **table,
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值使用
unsigned long sizemask;
// 哈希表已有节点的数量
unsigned long used;
} dictht;
3.1.2 哈希表节点
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
unit64_t u64;
int64_t s64;
} v;
// 下一个节点
struct dictEntry *next;
}
3.1.3 字典
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht *ht[2];
// 当没有进行rehash时,值为-1
int rehashidx;
}
3.2 字典特性
- redis中的字典使用哈希表作为底层实现,每个字典有两个哈希表,一个正常使用,一个rehash使用
- 哈希过程不是一次性完成,而是采用渐进式hash的方式实现
- 当同一索引上产生冲突时,采用链地址发解决冲突
四、跳跃表
4.1 初识跳跃表
跳跃表作为一种数据结构,通过分层的方式,对于n个排序的元素,实现O(logn)的查找,删除和插入操作。其数据结构利用图表示如下:
注意:每一列对齐的代表一个节点,而不是多个节点
4.2 redis中跳表结构描述以及示意图
接下来主要看看redis中,是如何定义对应的结构体的
每一个节点的定义如下:
typedef struct zkskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 后退指针
struct zkskiplistNode *backward;
// 层
struct zkskiplistLevel {
struct zkskiplistNode *forward;
unsigned int span;
} level[];
} zkskiplistNode
整个跳表的定义如下:
typedef struct zkskiplist {
// 表头节点和表尾节点
strcut zkskiplistNode *head, *tail;
// 长度
unsigned long length;
// 最高层数
int level;
} zkskiplist
跳表的示意图如下:
4.3 跳跃表特性总结
- 跳跃表是有序集合的实现方式之一
- 跳跃表的节点按照分值大小进行排序,若分值相同,则按照成员对象大小进行排序
五、整数集合
5.1 场景
当一个集合只包含整数值元素,且包含的元素并不多时,redis就会使用整数集合作为集合键的底层实现
5.2 结构描述以及示意图
typedef struct intset {
// 编码方式
uint32_t encoding;
// 长度
uint32_t length;
// 保存元素的数组
int8_t contents[];
}intset;
5.3 总结
- 整数集合的底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型,即升级
- 整数集合只支持升级操作,不支持降级操作
六、压缩列表
6.1 场景
压缩列表是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,且每个列表要么是小整数值,要么就是长度比较短的字符串,此时则采用压缩列表作为列表键的实现。同理,当哈希建只包含少量键值对,且每个键值对的键和值要么是小整数值,要么是长度比较短的字符串,此时采用压缩列表作为哈希建的实现
6.2 结构描述以及示意图
压缩链表的结构如图所示:
每一个entry节点的结构如图所示: