Redis5中数据结构底层
https://juejin.cn/post/6844904008591605767
1. String
SDS ( simple dynamic string ), 注意英文原意。 结构如下
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
优点:
-
常数复杂度获取字符串长度
-
惰性空间释放, 如果字符串缩短, 先不释放,而是记录在free中, 待下次直接使用
-
二进制安全, redis通过len来判断字符长度,而不是 \0结束符,这使得redis不仅可以保存文本数据,还可以保存任意格式的二进制数据
链接:https://zhuanlan.zhihu.com/p/51075839
扩容分析:
整体思路:
1 惰性缩容。不释放空间,留给到期释放等机制释放。
2 加倍扩容。在需要空间达1M之前按新空间两倍分配空间,否则按新空间大小+1M分配。注意,1M=10241024Char。Char可以是5bits/8bits/16bits/32bits/64bits
2. ziplist 压缩列表
redis的列表键和哈希键的底层实现之一
当一个列表键只包含少量列表项, 并且每个列表项要么就是小整数值, 要么就是长度比较短的字符串, 那么 Redis 就会使用压缩列表来做列表键的底层实现。
另外, 当一个哈希键只包含少量键值对, 并且每个键值对的键和值要么就是小整数值, 要么就是长度比较短的字符串, 那么 Redis 就会使用压缩列表来做哈希键的底层实现。
每个压缩列表节点都由 previous_entry_length
、 encoding
、 content
三个部分组成
节点的 previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。 previous_entry_length属性的长度可以是1字节或者5字节。
- 如果前一节点的长度小于254字节,那么 previous_entry_length属性的长度为1字节,前一节点的长度就保存在这一个字节里面。
- 如果前一节点的长度大于等于254字节,那么 previous_entry_length属性的长度为5字节:其中属性的第一字节会被设置为0xFE(十进制值254),而之后的四个字节则用于保存前一节点的长度
节点的encoding属性记录了节点的content属性所保存数据的类型以及长度。
-
一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码这种编码表示节点的 content属性保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录。
-
一字节长,值的最高位以11开头的是整数编码:这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他位记录。
节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定
参考:https://www.cnblogs.com/hunternet/p/11306690.html
5. Zset,有序列表
-
什么是跳表?
-
跳表查找、插入、删除元素的流程 和 时间复杂度
【查找】查到是趋于二分查找,时间复杂度 O(log(n))
【插入】插入的同时需要维护索引, 维护的时候有一个随机函数
randomLevel()
。 该函数需要确保,第二层的节点数是最底层的1/2, 第三层的节点数是最底层的1/4。 层级最高是32,每次随机一个值,然后以随机值所在的层,往下每层都加入元素 -
插入元素的时候,如何动态维护索引
总结:
- 跳表是可以实现二分查找的有序链表;
- 每个元素插入时随机生成它的level;
- 最底层包含所有的元素;
- 如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
- 每个索引节点包含两个指针,一个向下,一个向右;(笔记目前看过的各种跳表源码实现包括Redis 的zset 都没有向下的指针,那怎么从二级索引跳到一级索引呢?留个悬念,看源码吧,文末有跳表实现源码)
- 跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近;
为什么redis使用跳表 而不是红黑树来实现有序集合?
-
Redis 中的有序集合(zset) 支持的操作:
- 插入一个元素
- 删除一个元素
- 查找一个元素
- 有序输出所有元素
- 按照范围区间查找元素(比如查找值在 [100, 356] 之间的数据)
其中,前四个操作红黑树也可以完成,且时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。按照区间查找数据时,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了,非常高效。