觉得对你有益的小伙伴记得点个赞+关注
后续完整内容持续更新中
希望一起交流的欢迎发邮件至javalyhn@163.com
1. Set数据结构介绍
1.1 两个重要配置
set-max-intset-entrties (默认为512)
1.2 set底层数据结构是只有intset吗
回答是否定的。
Redis用intset或hashtable存储set。如果元素都是整数类型,就用intset存储
。
如果不是整数类型,就用hashtable(数组+链表的存来储结构)。key就是元素的值,value为null
。
1.3 IntSet源码解析
IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变、有序等特征。 结构如下:
其中的encoding包含三种模式,表示存储的整数大小不同:
为了方便查找,Redis会将intset中所有的整数按照升序依次保存在contents数组中,结构如图:
现在,数组中每个数字都在int16_t的范围内,因此采用的编码方式是INTSET_ENC_INT16,每部分占用的字节大小为:
- encoding:4字节
- length:2字节
- contents:2字节 * 3 = 6字节
我们向该其中添加一个数字:50000,这个数字超出了int16_t的范围,intset会自动升级编码方式到合适的大小。
以当前案例来说流程如下:
- 升级编码为INTSET_ENC_INT32, 每个整数占4字节,并按照新的编码方式及元素个数扩容数组
- 倒序依次将数组中的元素拷贝到扩容后的正确位置
- 将待添加的元素放入数组末尾
- 最后,将inset的encoding属性改为INTSET_ENC_INT32,将length属性改为4
源码如下
1.4 IntSet总结
Intset可以看做是特殊的整数数组,具备一些特点:
- Redis会确保Intset中的元素唯一、有序
- 具备类型升级机制,可以节省内存空间
- 底层采用二分查找方式来查询
2. ZSet数据结构介绍
2.1 两个重要配置
zset-max-ziplist-entrties (默认128)
zset-max-ziplist-value (默认64)
2.2 zset底层只会使用skiplist吗
回答是否定的
zset底层采用 skiplist 与 ziplist
当有序集合中包含的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值
(默认值为 128 ),
或者有序集合中新添加元素的 member 的长度大于服务器属性 server.zset_max_ziplist_value 的值
(默认值为 64 )时,redis会使用跳跃表
作为有序集合的底层实现。否则会使用ziplist作为有序集合的底层实现。
在此我们减小配置大小用来演示
2.3 说说链表和数组的优缺点?为什么引出跳表
解决方案也就是升维(空间换时间)
2.4 skiplist
SkipList(跳表)首先是链表,但与传统链表相比有几点差异: 元素按照升序排列存储 节点可能包含多个指针,指针跨度不同。
skiplist是一种以空间换取时间的结构。
由于链表,无法进行二分查找
,因此借鉴数据库索引
的思想,提取出链表中关键节点(索引),先在关键节点上查找,再进入下层链表查找。
总结来讲 跳表 = 链表 + 多级索引
2.5 skiplist时间复杂度分析
跳表查询的时间复杂度分析
首先每一级索引我们提升了2倍的跨度,那就是减少了2倍的步数,所以是n/2、n/4、n/8以此类推;
第 k 级索引结点的个数就是 n/(2^k);
假设索引有 h 级, 最高的索引有2个结点;n/(2^h) = 2
从这个公式我们可以求得 h = log2(N)-1
;
所以最后得出跳表的时间复杂度是O(logN)
2.6 skiplist空间复杂度分析
跳表查询的空间复杂度分析
首先原始链表长度为n
如果索引是每2个结点有一个索引结点,每层索引的结点数:n/2, n/4, n/8 ... , 8, 4, 2 以此类推;
或者索引是每3个结点有一个索引结点,每层索引的结点数:n/3, n/9, n/27 ... , 9, 3, 1 以此类推;
所以空间复杂度是O(n)
;
2.7 skiplist小总结
- 跳跃表是一个双向链表,每个节点都包含score和ele值
- 节点按照score值排序,score值一样则按照ele字典排序
- 每个节点都可以包含多层指针,层数是1到32之间的随机数
- 不同层指针到下一个节点的跨度不同,层级越高,跨度越大
- 增删改查效率与红黑树基本一致,实现却更简单
跳表是一个最典型的空间换时间
解决方案,而且只有在数据量较大的情况下
才能体现出来优势。而且应该是读多写少
的情况下才能使用,所以它的适用范围应该还是比较有限
的
维护成本相对要高 - 新增或者删除时需要把所有索引都更新一遍
;
最后在新增和删除的过程中的更新,时间复杂度也是O(log n)
3. 终章总结
3.1 redis数据类型以及数据结构的关系
3.2 不同数据类型对应的底层数据结构
- 字符串
int:8个字节的长整型。
embstr:小于等于44个字节的字符串。
raw:大于44个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
- 哈希
ziplist(压缩列表):
当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
hashtable(哈希表):
当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
- 列表
ziplist(压缩列表):
当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
linkedlist(链表):
当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现。quicklist ziplist和linkedlist的结合以ziplist为节点的链表(linkedlist)
- 集合
intset(整数集合):
当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
hashtable(哈希表):
当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
- 有序集合
ziplist(压缩列表):
当有序集合的元素个数小于zset-max-ziplist- entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配 置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
skiplist(跳跃表):
当ziplist条件不满足时,有序集合会使用skiplist作 为内部实现,因为此时ziplist的读写效率会下降。
至此,有关Redis经典五种数据类型及底层实现就讲完了,如歌对各位小伙伴有帮助,不妨赞关注一下!