上层数据结构以及分类
String | List | Hash | Set | Sorted Set |
---|---|---|---|---|
SDS | ListedList/ZipList\QuickList | HashTable/ ZipList | ZipList/ Intset | ZipList/SkipList |
String-SDS
SDS是什么
包含的属性
- 长度的类型,目前保存的字符串的长度的范围类型
- 当前SDS已经申请了包括预留的位置的长度
- 此时实际已经保存的长度
- 实际保存的信息
SDS相比c语言的字符串有什么好处
- 可以在 o(1)的时间复杂度下直接获取到字符串的长度
- 支持动态扩容,当想要对字符串进行增加操作时,会额外申请更多的空间,如果扩展字符串长度小于1M,新空间为原长度的两倍+1。如果当前字符串的长度大于1M,则为扩展后字符串的长度+1M+1。
- 通过每次扩容额外申请空间,可以减少字符串的扩容次数
- 二进制安全,即不需要根据什么标记来判断字符串结束
IntSet
IntSet是Set数据结构的一种实现方式,在Set中数据都为整数时会使用。
IntSet包含的属性
- 数组中包含的数据类型,即数字的长度,类似于 short int long
- 数组中此时包含的数字数量
- 实际存储的数字
IntSet扩容的过程
- 根据扩容的长度计算,逆序将每个数字放置到新的位置
- 将新的元素放置到数字的末尾
- 长度+1
好处
- 支持扩容,节约空间
- 保证IntSet数组中的元素 唯一并且有序
List-QuickList
SkipList
什么是跳表
- 跳表的本质可以进行
**二分查找的有序链表**
- 跳表底层有一个原始链表,原始链表中包含所有的元素,这是一个有序链表
- 除了最底层的原始链表以外,还有许多索引链表,在跳表结构中,第一层索引链表的元素是原始链表的一半,第二层索引链表的元素是第一层索引链表元素的一半,以此类推
-
复杂度
查询的时间复杂度是o(logn),插入的时间复杂度是o(logn)
空间复杂度是o(logn)
SkipList如何实现
SkipList节点 包含索引的头尾节点,长度和最大的层级(随机生成)
跳表的节点包括节点值 分数,前后指针,对于高层的链表会有多个下一个节点。
查找元素
- 从链表的最顶层索引开始,在当前链表中找到最大的节点
- 如果当前的节点数值与要查询的数值相等,则直接返回
- 否则则继续往下一级索引进行搜索
插入元素
索引的插入
- 通过定位到链表的位置进行插入,插入的时间复杂度是o(1),查询的时间复杂度是o(logn)
- 为了防止链表退化成单链表,除了对原始链表进行添加,还需要对先前的链表进行插入
索引的维护
- 维护索引要保证从原始链表开始,每向上一级会减少一半的元素。
- 因此在第一级索引中会有1/2的长度,在第二级索引中会有1/4的长度,以此类推,因此第n级索引的概率是1/2^n的长度
- Redis利用随机数判断索引应当维护在哪一级索引,因为生成在顶层索引生成时,会从顶层索引开始往下每一级都生成索引,因此其实每一层的真正长度是当前索引的概率和顶层所有的概率之和。
- 因此根据步骤3的规律,有1/2的概率不生成索引,1/4的概率生成第一级索引,根据该规律生成下方的规律。
// 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 :
// 1/2 的概率返回 1
// 1/4 的概率返回 2
// 1/8 的概率返回 3 以此类推
private int randomLevel() {
int level = 1;
// 当 level < MAX_LEVEL,且随机数小于设定的晋升概率时,level + 1
while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)
level += 1;
return level;
}
删除元素
- 通过二分查找找到元素的位置,时间复杂度是o(logn)
- 在找到列表后,将当前节点往下的所有节点进行删除
为什么选用跳表而不选用红黑树
**zset**
中常见的操作有,**插入**
,**查找**
,**删除**
,**顺序遍历**
,**范围查询**
- 前四个操作红黑树也能胜任,但是在进行
**范围查询**
时,跳表只需要定位到原始链表中的位置,然后持续向后遍历即可,效率比红黑树高。