深入理解Redis 底层数据结构之QuickList与 ZipList

深入理解Redis 底层数据结构之QuickList 与ZipList

centos7安装redis6.25结合docker镜像配置方法(附redis.conf文件)

list底层的数据结构:

  1. linkedList 链表

  2. zipList 压缩列表

  3. quickList 快链表

在3,2版本之前,列表是通过双向链表或压缩链表来实现的。他们之间可以互相转换,在3.2版本之后又新加入了新的数据结构:QuickList,也就是双向链表和压缩列表的结合。

链表:LinkedList
深入理解Redis 底层数据结构之QuickList与 ZipList_第1张图片
  • 数据结构中常用的带头节点的双向链表,并且带有指向头节点的头指针和指向最后一个节点的尾指针,双向链表保证从任意一个位置可以向前或者向后进行遍历,但要注意头节点的前指针为null,尾节点的后指针为空,这也就说明了redis中的LinkedList数据结构是一个无环链表,不支持逆序遍历

  • 第二点就是说,操作命令中的lpush(left push) 、lpop(left pop )等命令中的left代表从队头进行操作,right代表从队尾进行操作

  • 结构中的 len 字段记录了整个链表的长度。

  • 由于链表本身的特性,对链表进行增删操作所需的时间非常少,时间复杂度仅为O(1);但如果是需要凭借索引寻找链表中某个值,时间花销就显得有些高,时间复杂度为O(n)

  • dup函数用于对某个链节点进行复制操作

  • free函数用于对某个链节点进行释放操作,也就是删除

  • match函数用于判断某个链节点的值和输入的值是否相等。


压缩列表:ZipList
深入理解Redis 底层数据结构之QuickList与 ZipList_第2张图片
  • redis中的压缩列表是用字节数组作为其数据结构。
  • 在zipList的数据结构中有以下字段:
    • zlbytes:整个压缩列表所占用的字节数,本身是4个字节即32位,因此压缩链表的最大长度为(2^32-1)个字节
    • zltail_offset:压缩列表尾部的元素到起始地址的偏移量,可以直接找到表尾元素,即支持逆序遍历
    • zlength:表中元素的个数,占2字节,如果列表中的元素个数超过(2^16-1)个,那么需要遍历整个列表才能得到元素的个数
    • entry:列表中存储的元素,可能是整数,也可能是字节数组,具体结果见下图
    • zlend:元素的结束标志,占一个字节,是一个常量OXFF
深入理解Redis 底层数据结构之QuickList与 ZipList_第3张图片
  • entry保存中列表中的元素,可能是整数,也可能是字节数组。其结构体有三个字段:

    • pre_length:上一个元素的长度,方便逆序遍历的时候直接找到上个节点的起始位置。当前一个元素的长度小于254个字节的时候,用一个字节表示;当前一个元素的长度大于等于254个字节的时候,用5个字节表示,此时前一个字节存储固定值254

    • encoding: 长度为两个 bit 。 它的值可以是 00 、 01 、 10 和 11 。00 、 01 和 10表示 content 部分保存着字符数组,11表示 content 部分保存着整数。

    • content:保存真正的元素。

转换条件:
创建一个列表是,默认使用的数据结构是zipList,但在满足以下条件的时候,zipList会自动转化为双向链表:

  1. 压缩列表中插入了一个过长的字符串(长度可配置,默认65)
  2. 压缩列表中的节点数超过某一数量(默认512)

总结

  1. 当存储的元素很少的时候,通常采用zipList作为基础数据结构,而当数据元素较多时则采用LinkedList。

  2. 从数据结构本身的特点而言:LinkedList在插入和删除元素方面具有很大的优势。但其需要额外存储两个指针,存储开销比较大,而且每个元素都是单独的内存空间,地址不连续,容易造成内存碎片。

  3. 而压缩列表存储在一块连续的内存空间中,但是增加删除元素开销较大,在内存扩容执行realloc操作的时候需要进行大量的拷贝操作

快速列表:QuickList
深入理解Redis 底层数据结构之QuickList与 ZipList_第4张图片

​ redis3.2之后,增加了新的数据结构:QuickList。它结合了LinkedList和zipList的优点。它将LinkedList的每个节点都用zipList的方式来存储。LinkedList节点之间依然采用双向链表连接起来,只不过一个节点上不是仅仅保存一个元素,而是多个,这些元素全部以压缩列表的方式存储在节点上。

QuickList结构体中的字段:

  • head:指向链表中的头节点
  • tail:指向链表中的最后一个节点
  • count:QuickList存储元素的个数
  • nodes:QuickList链表节点的个数
  • compressDepth:采用的压缩算法的深度。为了进一步节约空间,redis还会对zipList进行lzf压缩,这是一种无损压缩算法。

​ QuickList中每个节点的数据结构:

  • prev:指向前一个节点的指针
  • next:指向下一个节点的指针
  • zl:压缩链表
  • size:zplist占用空间大小
  • count:zipList中元素的个数
List常用操作及示例

插入头部lpush key value1 [value2....]

如果key不存在 则先创建再插入

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3

插入头部lpushx key value1 [value2....]

如果key不存在 则不做任何操作

127.0.0.1:6379> lpushx queue 1 2 3 
(integer) 0
127.0.0.1:6379> exists quequ
(integer) 0
127.0.0.1:6379> lpushx queue 1 2 3 
(integer) 0
127.0.0.1:6379> exists quequ
(integer) 0

插入尾部 rpush

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> rpush list 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "4"

查看队列的指定范围 lrange start end

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"

移除列表头的第一个元素:lpop key

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "4"
127.0.0.1:6379> lpop list 
1) "3"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
3) "4"

移除列表尾 的第一个元素:rpop key

127.0.0.1:6379> lrange list 0 -1 
1) "2"
2) "1"
3) "4"
127.0.0.1:6379> rpop list
"4"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"

通过下标获取队列中的某个值:lindex key index

127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> lindex list 1
"1"
127.0.0.1:6379> lindex list 0
"2"

获取队列长队 llen key

127.0.0.1:6379> lpush list 7 8 9 0 11
(integer) 7
127.0.0.1:6379> llen list
(integer) 7

移除指定的值:lrem key num value

从key队列中移除num个值为value的元素 默认先移除队头的

127.0.0.1:6379> lpush list 1 1 3 2 3 4 1 1 
(integer) 8
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "1"
3) "4"
4) "3"
5) "2"
6) "3"
7) "1"
8) "1"
127.0.0.1:6379> lrem list 1 1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "4"
3) "3"
4) "2"
5) "3"
6) "1"
7) "1"
127.0.0.1:6379> lrem list 2 1 
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
4) "3"
5) "1"

将列表按照指定位置截断:ltrim

 127.0.0.1:6379> lpush list 1 2 3 4 5 6 
(integer) 6
127.0.0.1:6379> ltrim list 3 4 
OK
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"

移除列表1的最后一个元素并添加到列表2 第一个位置:rpoplpush list1 list2

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
127.0.0.1:6379> rpoplpush list list1 
"2"
127.0.0.1:6379> lrange list 0 -1
1) "3"
127.0.0.1:6379> lrange list1 0 -1
1) "2"

更新列表指定位置:lset key index value

1. 如果列表不存在,则报错

2.是在列表已有的索引范围内进行值的覆盖

127.0.0.1:6379> lrange list 0 -1
1) "l"
127.0.0.1:6379> lset list 1 2
(error) ERR index out of range
127.0.0.1:6379> lset list 0 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "2"

给某个元素的前/后插入值 :linsert key before/after privot value

当列表中有多个相同值时,会在对头的第一个值进行相应的插入操作

127.0.0.1:6379> lrange list 0 -1
1) "2"
127.0.0.1:6379> linsert list before 2 before
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "before"
2) "2"
127.0.0.1:6379> linsert list after 2 after
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "before"
2) "2"
3) "after"

#当列表中有多个相同值时,会在对头的第一个值进行相应的插入操作
127.0.0.1:6379> lpush list2  1 3 4 1 3 4 
(integer) 6
127.0.0.1:6379> lrange list2 0 -1
1) "4"
2) "3"
3) "1"
4) "4"
5) "3"
6) "1"
127.0.0.1:6379> linsert list2 before 1 before
(integer) 7
127.0.0.1:6379> lrange list2 0 -1
1) "4"
2) "3"
3) "before"
4) "1"
5) "4"
6) "3"
7) "1"

你可能感兴趣的:(redis,redis,数据结构,list)