简介redis之集合类型数据
在之前的文章中我们有提及string类型是通过底层的SDS(简单动态字符串)实现的,那么其他呢?
除了String类型外,list,set,sorted set,hash这四种都有两种底层实现结构,通常情况我们称这四种类型为集合类型, 特点是一个键对应了一个集合的数据 :
list由双向链表和压缩列表实现
hash由压缩列表和哈希表实现
sorted set由跳表和压缩列表实现
set 由整数数组和哈希表实现
·1.压缩列表在查找和更新的时间复杂度方面没有很大的优势,为什么redis要把他作为底层数据结构?
2.集合数据的操作效率
list由双向链表和压缩列表实现
阅读src/ziplist.h和src/ziplist.c我们惊奇的发现一件事情:
redis竟没有提供一个结构体保存压缩列表的信息,而是提供了一组宏来定义每个成员的地址
压缩列表是一系列 特殊编码的连续内存块 组成的顺序序列数据结构,可以包含任意多个节点(entry),每一个节点可以保存一个字节数组或者一个整数值。
这个数据结构是不是乍一看很像数组,每一个元素entry保存了一个数据,和数组不同的是表头有三个字段:
1.zlbytes标识整个压缩列表长度
2.zltail标识列表的偏移量,表尾点entryN距离压缩列表起始地址的字节数
3.zllen标识列表中entry个数
列表表尾还有一个zlend标识结束
那么我们就可以快速定位第一个元素(zlbytes-10)和最后一个元素(zlbytes-1)了
ziplist的每个节点由3个部分组成:
1.prevlen表示前一个节点的长度
2.encoding表示节点的编码格式
3.entry-data:压缩后的数据
zpilist格式
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
zlbytes:ziplist占总字节数
zltail:最后一个元素的偏移量,相当于ziplist的尾指针。
zllen:entry元素个数
zlend :ziplist结束标志位
entry:ziplist的各个节点
ziplist的entry 的格式:
<prevlen> <encoding><len> <entry-data>
prevlen :前一个元素的长度,相当于节点保存前一个元素的指针。
encoding: 记录了当前节点保存的数据的类型以及当前节点长度,相当于节点保存后一个元素的指针。
len:自身长度
entry-data :经过压缩后的数据
quciklist结构实现
其中节点实现
我们阅读结构体发现虽然是quicklist但却出现了很多关于ziplist的身影
首先ziplist的插入和删除操作时间复杂度非常高(参考数组需要重新生成一个ziplist来作为更新后的list),当一个list非常大的且更新平凡就会带来非常大的开销。
所谓的quicklist就是把ziplist和普通的双向链表解和,每个双向节点保存一个ziplist,每个ziplist中存一批list数据,这样可以避免大量链表指针操作带来的内存开销(即化整为零思想)
Redis对外暴露的list数据结构,其底层实现所依赖的内部数据结构就是quicklist。quicklist就是一个块状的双向压缩链表。
考虑到双向链表在保存大量数据时需要更多额外内存保存指针并容易产生大量内存碎片,以及ziplist的插入删除的高时间复杂度,两个数据结构的缺陷会导致在数据量很大或插入删除操作频繁的极端情况时,性能极其低下。
Redis为了避免数据结构在极端情况下的低性能,将双向链表和ziplist综合起来,成为了较双向链表及ziplist性能更加稳定的quicklist
1.压缩列表在查找和更新的时间复杂度方面没有很大的优势,为什么redis要把他作为底层数据结构?
list底层使用压缩列表本质上是将所有元素紧挨着存储,能节省空间避免内存碎片(内存空间不是硬盘空间是很昂贵的)
有序列表查找元素,只能遍历查找,于是就出现了跳表
skiplist节点结构体
也就是说跳表在链表的基础上增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位
其中索引可以持续累加
整数数组是集合的底层实现之一,当一个集合只包含整数元素且数量不多,就会用该方式实现
2.集合数据的操作效率