1:Redis链表实现的特性
双端:链表节点带有 prev 和 next 指针,获取某个节点的前置节点和后置节点复杂度都是O(1)。
无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以NULL为终点。
带表头指针和表尾指针:通过list结构的 head 和 tail 指针,程序获取链表的表头节点和表尾结点的复杂度都是O(1)。
带链表长度计数器:程序使用 list 结构的 len属性对 list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
多态:链表节点使用 void* 指针来保存节点值,并且通过 list 结构的 dup、 free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
2: 应用场景
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
排行榜,数据最新列表等等
集合设置过期,只能给整个集合设置,不能单独给某一个元素设置,没有给单独元素设置过期时间的策略
3:存储方式
其底层有linkedList、zipList和quickList这三种存储方式
1:linkedList
与Java中的LinkedList类似,Redis中的linkedList是一个双向链表,也是由一个个节点组成的。Redis中借助C语言实现的链表节点结构如下所示
pre指向前一个节点,next指针指向后一个节点,value保存着当前节点对应的数据对象。listNode的示意图如下所示
链表的结构如下:
head指向链表的头节点
tail指向链表的尾节点
dup函数用于链表转移复制时对节点value拷贝的一个实现,一般情况下使用等号足以,但在某些特殊情况下可能会用到节点转移函数,默认可以给这个函数赋值NULL即表示使用等号进行节点转移
free函数用于释放一个节点所占用的内存空间,默认赋值NULL的话,即使用Redis自带的zfree函数进行内存空间释放
match函数是用来比较两个链表节点的value值是否相等,相等返回1,不等返回0
len表示这个链表共有多少个节点,这样就可以在O(1)的时间复杂度内获得链表的长度
链表的结构如下:
2:zipList类型
Redis的zipList结构如下所示
zipList的结构如下所示
注意到zltail_offset这个参数,有了这个参数就可以快速定位到最后一个entry节点的位置,然后开始倒序遍历,也就是说zipList支持双向遍历。
3:linkList与zipList的对比
1:当列表对象中元素的长度较小或者数量较少时,通常采用zipList来存储;当列表中元素的长度较大或者数量比较多的时候,则会转而使用双向链表linkedList来存储。
①列表对象保存的所有字符串元素的长度都小于64字节
②列表元素保存的元素数量小于512个
2:双向链表linkedList便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还有额外保存两个指针;其次,双向链表的各个节点都是单独的内存块,地址不连续,容易形成内存碎片。
3:zipList存储在一块连续的内存上,所以存储效率很高。但是它不利于修改操作,插入和删除操作需要频繁地申请和释放内存。特别是当zipList长度很长时,一次realloc可能会导致大量的数据拷贝。
4:为什么有了linkedList还有设计一个zipList呢?就像zipList的名字一样,它是一个压缩列表,是为了节约内存而开发的。相比于linkedList,其少了pre和next两个指针。在Redis中,pre和next指针就要占用16个字节(64位系统的一个指针就是8个字节)。
另外,linkedList的每个节点的内存都是单独分配,加剧内存的碎片化,影响内存的管理效率。与之相对的是,zipList是由连续的内存组成的,这样一来,由于内存是连续的,就减少了许多内存碎片和指针的内存占用,进而节约了内存。
4:quickList
1:起源
在Redis3.2版本之后,list的底层实现方式又多了一种,quickList。qucikList是由zipList和双向链表linkedList组成的混合体。它将linkedList按段切分,每一段使用zipList来紧凑存储,多个zipList之间使用双向指针串接起来。
示意图如下所示:
2:每个zipList可以存储多少个元素
redis.conf文件,在DVANCED CONFIG下面有着清晰的记载
quickList内部默认单个zipList长度为8k字节,即list-max-ziplist-size的值设置为-2,超出了这个阈值,就会重新生成一个zipList来存储数据。
根据注释可知,性能最好的时候就是就是list-max-ziplist-size为-1和-2,即分别是4kb和8kb的时候,当然,这个值也可以被设置为正数,当list-max-ziplist-szie为正数n时,表示每个quickList节点上的zipList最多包含n个数据项。
4:小结
1:linkedList
1:linkedList便于在表的两端进行 push 和 pop 操作,但是它的内存开销比较大
2:linkedList每个节点上除了要保存数据之外,还要额外保存两个指针
3:linkedList的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片
2:ziplist
1:ziplist 由于是一整块连续内存,所以存储效率很高
2:ziplist 不利于修改操作,每次数据变动都会引发一次内存的 realloc
3:当 ziplist 长度很长的时候,一次 realloc 可能会导致大批量的数据拷贝,进一步降低性能
3:quicklist
1:空间效率和时间效率的折中
2:结合了双端链表和压缩列表的优点