Redis数据类型-列表详解

778 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

列表里可以存储多个且有序的字符串,每个字符串称之为元素,在redis中每个列表最多可以存储2³²-1个元素。

列表是一个比较灵活的数据结构,可以充当队列的角色,实际开发中有很多应用场景。

首先我们先看一看它的命令:

操作命令
添加rpush lpush linsert
查找lrange lindex llen
删除lpop rpop lren ltrim
修改lset
阻塞blpop brpop

详细分析:

1 命令

1.1 添加

在列表这个数据结构中,添加元素时可以从左边添加也可以从右边添加,也可以向某个指定位置添加

1.1.1 从右边插入元素

rpush key value可以向列表右侧添加元素

127.0.0.1:6379> rpush name a b c d
(integer) 4

此时可以使用lrange key 0 -1从左到右输出元素

127.0.0.1:6379> lrange name 0 -1
1) "a"
2) "b"
3) "c"
4) "d"

有关这个输出命令之后会说到

1.1.2 从左边插入元素

lpush key value可以向列表左侧添加元素

127.0.0.1:6379> lpush name:1 a b c d
(integer) 4
127.0.0.1:6379> lrange name:1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"

这个命令和rpush基本相同只是从左侧插入了

1.1.3 向某个位置前或者后添加元素

linsert key before|after pivot value这个命令可以在元素pivot 前或后插入value值

下边这个命令就是在元素b前边添加元素luke

127.0.0.1:6379> linsert name before b luke
(integer) 5
127.0.0.1:6379> lrange name 0 -1
1) "a"
2) "luke"
3) "b"
4) "c"
5) "d"

可是要是pivot有多个会怎样呢?

127.0.0.1:6379> rpush name:3 a b c d b
(integer) 5
127.0.0.1:6379> linsert name:3 before b luke
(integer) 6
127.0.0.1:6379> lrange name:3 0 -1
1) "a"
2) "luke"
3) "b"
4) "c"
5) "d"
6) "b"

可以看到只给第一个元素b前添加了元素luke。

1.2 查找

当查找的时候,可以从左到右也可以从右到左,在redis中,从左向右查找的时候,第一个元素的下标是0,最后一个是N-1。从右向左查找的时候,是从-1到-N。也就是说第一个元素是0,最后一个是-1。

1.2.1 获取指定范围内的元素列表

上边我们使用了lrange key start end命令去获取list的值

127.0.0.1:6379> lrange name 0 -1
1) "a"
2) "luke"
3) "b"
4) "c"
5) "d"

这里0是指第一个元素,-1指最后一个元素,即这个命令是获取所有元素。

1.2.2 获取指定位置的元素

我们也可以获取指定下标的元素:lindex key index

例如我们从name中获取第二个元素,则获取到的值为b

127.0.0.1:6379> rpush name a b c d
(integer) 4
127.0.0.1:6379> lindex name 1
"b"

1.2.3 获取列表长度

llen key可以获取列表内共有多少个元素,即列表的长度

127.0.0.1:6379> llen name
(integer) 4

1.3 删除

1.3.1 从左侧弹出元素

这个弹出指的就是,取出第一个元素,并且其余元素的下标改变,之前的1位置变为0。
lpop key可以从列表左侧弹出一个元素,并且只能弹出第一个元素。

127.0.0.1:6379> lpop name
"a"

之前的name列表将变为

127.0.0.1:6379> lrange name 0 -1
1) "b"
2) "c"
3) "d"

1.3.2 从列表右侧弹出

从右侧弹出是rpop key吗,与左侧弹出相同,只是弹出的是最右侧的一个元素。

1.3.3 删除指定元素

lrem key count value会遍历整个列表,删除value值 根据count值的不同会有三种情况

  • count > 0 从左到右删除count个value
  • count < 0 从右到左删除count绝对值个value
  • count = 0 删除所有与value值相同的元素

我们给name左边添加两个c

127.0.0.1:6379> lpush name c c
(integer) 5
127.0.0.1:6379> lrange name 0 -1
1) "c"
2) "c"
3) "b"
4) "c"
5) "d"

然后删除右边两个c

127.0.0.1:6379> lrem name -2 c
(integer) 2
127.0.0.1:6379> lrange name 0 -1
1) "c"
2) "b"
3) "d"

1.3.4 按照索引范围修剪列表

其实就是获取到指定位置的字符串,重新赋值给key
命令是:ltrim key start end 我们进行如下操作,

127.0.0.1:6379> rpush name a b c d
(integer) 4
127.0.0.1:6379> ltrim name 1 2
OK
127.0.0.1:6379> lrange name 0 -1
1) "b"
2) "c"

可以看到,name中的元素只剩原本下标1-2的值,b和c了

1.4 修改

修改就比较简单了,修改指定下标的元素,也就是修改指定位置的元素
命令是lset key index value,意思是修改index位置的值为value

127.0.0.1:6379> rpush name a b c d
(integer) 4
127.0.0.1:6379> lset name 1 luke
OK
127.0.0.1:6379> lrange name 0 -1
1) "a"
2) "luke"
3) "c"
4) "d"

1.5 阻塞

阻塞主要是阻塞式弹出,阻塞的意义在于,当没有获取到值的时候,会阻塞等待,无法执行其他命令
blpop key timeout从左侧阻塞式弹出
brpop key timeout从右侧阻塞式弹出
也就是说,客户端会等待timeout的时间获取结果,若是没有值,会等待timeout的时间,若是在这个时间内有值添加进来,则直接返回。若是没有值,则等待timeout的时间,然后返回nil。当timeout的值为0的时候,会一直阻塞等待值的添加。

分类说说

1.5.1 列表一直为空

就像下边的例子,当列表一直为空的时候,会等待3秒然后返回nil

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> brpop name 3
(nil)
(3.08s)

1.5.2 列表不为空的时候会立即返回

127.0.0.1:6379> rpush name a b c d
(integer) 4
127.0.0.1:6379> brpop name 3
1) "name"
2) "d"

在brpop的时候,若是遍历了多个键,只要有一个key的value内有值则会直接返回。而且当多个客户端获取同一个key的元素的时候,只有第一个发起命令的客户端可以获取到值。

2 内部编码

列表类型的内部编码有两种

  • ziplist
    • 元素个数需要小于配置list-max-ziplist-entries,默认是512个
    • 每个元素的大小需要小于配置list-max-ziplist-value,默认是64字节
  • linkedlist
    • 不满足ziplist的条件时,会使用linkedlist的内部编码

这个和哈希类型的内部编码类似,都是由配置和元素个数和元素大小决定内部编码。ziplist的优点是占用空间更小

3 使用场景

3.1 消息队列

消息队列遵循先入先出
lpush + brpop 则可以实现消息队列
从阻塞弹出就可以看出这个可以实现消息队列,消费者阻塞获取值,当生产者插入元素之后,立即返回并消费消息。

3.2 数据分页

因为列表不仅是有序的,而且还可以获取指定范围内的元素,这就很完美的契合了分页查询。

3.3 栈

lpush + lpop则可以实现栈
栈则和消息队列相反,是先入后出。

list类型的使用场景还是蛮多的,大家在工作中还有哪些更巧妙的使用场景呢?