【Redis02】Redis基础:List相关操作

2023-03-16 20:01:47 浏览数 (2)

Redis基础学习:List相关操作

在 Redis 中,List 也是非常常用的一个数据类型,它可以看做是我们 PHP 中的数字下标类型的数组,注意,是数字下标的那种最典型的数组格式。重要的是,它可以方便地帮助我们实现队列或者栈的功能,非常强大。同样的,我们还是先来学习一下它的一些基本操作命令。

添加元素

添加元素就是两个命令,RPUSH 和 LPUSH ,分别从 List 的右边或者左边添加数据。

代码语言:javascript复制
127.0.0.1:6379> lpush a 1 2 3
(integer) 3
127.0.0.1:6379> rpush a -3 -2 -1
(integer) 6
127.0.0.1:6379> llen a
(integer) 6
127.0.0.1:6379> lrange a 0 5
1) "3"
2) "2"
3) "1"
4) "-3"
5) "-2"
6) "-1"

上面的例子中,LPUSH 从左边添加,1最先被添加进去,然后是 2 ,因此,返回的顺序是 3 2 1 这样的。同理,RPUSH 的时候,我们是从右边添加,-3 -2 -1 从右边依次进入的顺序就是我们写的 -3 -2 -1 这个顺序。这两个命令操作成功后都会返回 List 的长度,如果指定的 key 不存在的话,就创建新的 List 。

LLEN 可以获取 List 的长度。LRANGE 返回 List 中的数据,它有两个参数,分别是开始位置和结束位置,其实我们可以使用 LRANGE a 0 -1 来获取 List 中的全部元素。注意,-1 表示的反向取数据,你可以把这种下标看成是一种 环形 取数据的方式。

代码语言:javascript复制
127.0.0.1:6379> lpushx b 1 2 3
(integer) 0
127.0.0.1:6379> rpushx b -1 -2 -3
(integer) 0

RPUSHX 和 LPUSHX 的意思是如果指定的 key 不存在,则无法添加数据。

修改指定下标元素

能设置,当然也能修改啦,修改使用的是 LSET 命令,它是根据指定的下标进行数据修改的。

代码语言:javascript复制
127.0.0.1:6379> lset a 3 -33
OK
127.0.0.1:6379> lrange a 0 -1
1) "3"
2) "2"
3) "1"
4) "-33"
5) "-2"
6) "-1"

127.0.0.1:6379> lset b 0 1
(error) ERR no such key

127.0.0.1:6379> lset a 22 1
(error) ERR index out of range

如果指定的下标不存在或者是指定的 key 不存在,就无法修改,并且会分别报出不同的错误。

根据指定元素值插入

插入数据是根据某个值的内容进行插入,注意,它不是根据下标哦。

代码语言:javascript复制
127.0.0.1:6379> linsert a before -33 -22
(integer) 7
127.0.0.1:6379> linsert a after -33 -11
(integer) 8
127.0.0.1:6379> lrange a 0 -1
1) "3"
2) "2"
3) "1"
4) "-22"
5) "-33"
6) "-11"
7) "-2"
8) "-1"

从上面的例子可以看出,LINSERT 命令有一个参数可以设置为 BEFORE 或者 AFTER ,分别代表根据指定值,从它的前面或者后面插入数据。

代码语言:javascript复制
127.0.0.1:6379> linsert a after -33 -33
(integer) 9
127.0.0.1:6379> lrange a 0 -1
1) "3"
2) "2"
3) "1"
4) "-22"
5) "-33"
6) "-33"
7) "-11"
8) "-2"
9) "-1"
127.0.0.1:6379> linsert a after -33 -111
(integer) 10
127.0.0.1:6379> linsert a before -33 -222
(integer) 11
127.0.0.1:6379> lrange a 0 -1
 1) "3"
 2) "2"
 3) "1"
 4) "-22"
 5) "-222"
 6) "-33"
 7) "-111"
 8) "-33"
 9) "-11"
10) "-2"
11) "-1"

插入成功后,会返回 List 的长度,如果列表 key 不存在,则不会有任何操作发生,如果指定的插入数据不存在,则会返回 -1 ,如果 key 存在,但它不是一个 List 类型的话,那么会报错。

获取元素

添加、修改、插入元素的操作非常简单,但其实我们最常用的就是添加那两个,也就是 LPUSH 和 RPUSH 。同样的,在获取元素中,我们最常用的也仅是那么几个命令,不过既然是基础的学习,我们还是一个一个的都过一遍。

获取指定下标的元素

通过 LINDEX 命令,就可以获取到指定下标的元素数据。

代码语言:javascript复制
127.0.0.1:6379> lindex a 7
"-33"
127.0.0.1:6379> lindex b 7
(nil)
127.0.0.1:6379> lindex a 22
(nil)
127.0.0.1:6379> lindex a -6
"-33"

和之前说过的一样,这里的下标也是可以使用负数反向获取的。

根据值获取下标

除了通过下标获取元素之外,还可以通过 LPOS 获取到指定元素的下标。

代码语言:javascript复制
127.0.0.1:6379> lpos a -33
(integer) 5

很显然,在默认情况下,它返回的是第一个元素的下标,因为在上面的操作中,我们已经有多个 -33 这条数据了。那么怎么获取到其它的 -33 的下标呢?

代码语言:javascript复制
127.0.0.1:6379> rpush a -33 baz -33 bar open
(integer) 16
127.0.0.1:6379> lrange a 0 -1
 1) "3"
 2) "2"
 3) "1"
 4) "-22"
 5) "-222"
 6) "-33"
 7) "-111"
 8) "-33"
 9) "-11"
10) "-2"
11) "-1"
12) "-33"
13) "baz"
14) "-33"
15) "bar"
16) "open"

首先,我们多添加一些数据,现在整个 List 中有四个 -33 元素。

代码语言:javascript复制
127.0.0.1:6379> lpos a -33 rank 2
(integer) 7
127.0.0.1:6379> lpos a -33 rank -2
(integer) 11

RANK 参数,表示我们现在要获取第几个指定的值的下标。例子中的正数 2 就是正数第二个 -33 的下标,也就是 7 ,-2 表示倒数第二个 -33 的下标值,也就是下标为 11 的数据。除了指定显示第几个之外,我们还可以指定返回多少个,使用 COUNT 参数。

代码语言:javascript复制
127.0.0.1:6379> lpos a -33 count 3
1) (integer) 5
2) (integer) 7
3) (integer) 11
127.0.0.1:6379> lpos a -33 count 4
1) (integer) 5
2) (integer) 7
3) (integer) 11
4) (integer) 13
127.0.0.1:6379> lpos a -33 count 0
1) (integer) 5
2) (integer) 7
3) (integer) 11
4) (integer) 13

默认情况下,COUNT 参数就是 1,也就是返回 1 条数据。如果我们指定了,就返回符合指定数据的查找数据。如果设置为 0 的话,就表示返回全部符合的数据下标。当然,RANK 和 COUNT 也可以组合使用。

代码语言:javascript复制
127.0.0.1:6379> lpos a -33 count 0 rank 2
1) (integer) 7
2) (integer) 11
3) (integer) 13

这个例子中,返回的是除了第一个之外的全部 -33 的下标。

代码语言:javascript复制
127.0.0.1:6379> lpos a -333
(nil)
127.0.0.1:6379> lpos a -333 count 1
(empty array)

如果查找的数据不存在,会返回 nil ,不过有个特殊情况,那就是如果使用了 COUNT 返回的话,它本身就会将结果变成数组返回,所以如果在这种情况如果没有找到数据,就会返回一个空数组。

弹出

弹出操作是实现栈或者队列的重要命令,也是大家会经常使用的两个命令,分别是 RPOP 和 LPOP ,很明显,就是分别从右、左弹出数据。弹出的意思就是返回这条数据并且在 List 中删除它。

代码语言:javascript复制
127.0.0.1:6379> lpop a
"3"
127.0.0.1:6379> lpop a 2
1) "2"
2) "1"
127.0.0.1:6379> rpop a
"open"
127.0.0.1:6379> rpop a 3
1) "bar"
2) "-33"
3) "baz"
127.0.0.1:6379> llen a
(integer) 9

127.0.0.1:6379> rpop b
(nil)

如果指定 key 不存在,那么这两个命令返回的就是 nil 空。

有了这两个命令,实现队列和栈就非常简单了吧。队列的话,如果是 RPUSH ,那就 LPOP ,反过来也行。栈就更简单了 RPUSH 配合 RPOP 就好啦!

数据移动

数据移动在 6.2 之前的版本是使用 RPOPPUSH ,但在 6.2 之后这个命令就被标为过时了,并且新出了一个 LMOVE 命令。我们就以最新的来进行学习吧。

LMOVE source destination LEFT|RIGHT LEFT|RIGHT

LMOVE 的命令调用方法看着有点蒙啊,其实很好理解。source 表示从哪里移动,destination表示移动到的目标,也就是移动到哪里去。后面两个参数分别代表是LPOP/RPOP source 和 LPUSH/RPUSH destination 。

代码语言:javascript复制
127.0.0.1:6379> lmove a a1 left left
"-22"
127.0.0.1:6379> lmove a a1 left left
"-222"
127.0.0.1:6379> lrange a1 0 -1
1) "-222"
2) "-22"
127.0.0.1:6379> lmove a a1 left right
"-33"
127.0.0.1:6379> lrange a1 0 -1
1) "-222"
2) "-22"
3) "-33"
127.0.0.1:6379> lmove a a1 right left
"-33"
127.0.0.1:6379> lrange a1 0 -1
1) "-33"
2) "-222"
3) "-22"
4) "-33"

我们先从之前的 a 中 LPOP 两条数据 LPUSH 到了 a1 中,然后再从 a 中 LPOP 一条数据 RPUSH 到 a1 中,最后,再从 a 中 RPOP 一条数据 LPUSH 到了 a1 中。

原子阻塞弹出

这是啥意思?我们使用 BLPOP 或者 BRPOP ,当队列空的时候,它会阻塞到这里,直到获取到下一条数据。

代码语言:javascript复制
127.0.0.1:6379> blpop a a1 0
1) "a"
2) "-111"
...........
127.0.0.1:6379> blpop a a1 0
1) "a1"
2) "-33"
127.0.0.1:6379> blpop a a1 0

我们先弹出所有的数据,注意,这个 BLPOP 是可以同时弹出多个队列的,我们在这个例子中,同时把 a 和 a1 的都弹完,当弹出的时候,返回的是一个列表,第一个值是列表的名称,第二个是弹出的值。两个队列中都没有数据后,BLPOP 就阻塞在这里了。最后一个参数的 0 表示的是超时时间,如果过了超时时间还没有新的元素插入到这两个 List 中,就会结束阻塞,而如果设置为 0 的话,就会一直等待。

代码语言:javascript复制
127.0.0.1:6379> lpush a1 1
(integer) 1

接着另开一个 redis-cli 客户端,然后为 a1 添加一条数据。

代码语言:javascript复制
127.0.0.1:6379> blpop a a1 0
1) "a1"
2) "1"
(16.57s)

之前的那个客户端中阻塞的队列马上响应并弹出了一条数据,并且下面还标明了本次等待的时间。

这个东西有什么用呢?在某些场景中,比如我们后台消费队列的场景一般就会是一个进程不停地执行消费,通常我们可能会写一个死循环,然后每次循环的时候查找队列中是否有内容,写个 LPOP 如果返回空,就 sleep() 一会,如果下次还没查到,就再 sleep() 一会。现在,我们可以替换成使用 BLPOP ,其实就是省去了 sleep() 的过程,并且也可以减少反复远程查询的次数。

有 POP 操作,对应的也有阻塞的 BLMOVE 操作,这个命令也是 6.2 之后才有的,替换之前的 BRPOPPUSH 命令。概念也是和 LMOVE 类似的,所以大家直接看下代码就好了。

代码语言:javascript复制
127.0.0.1:6379> blmove a a1 left left 0

首先我们让 a 的数据全部弹出,现在 BLMOVE 也是在阻塞状态。

代码语言:javascript复制
127.0.0.1:6379> lpush a1 1
(integer) 1
127.0.0.1:6379> lpush a 1
(integer) 1

接着还是通过另一个客户端插入数据,注意,我们给 a1 插入数据的时候不会有动静,因为我们需要给 source 插入数据才会发生数据移动。

代码语言:javascript复制
127.0.0.1:6379> blmove a a1 left left 0
"1"
(11.85s)
127.0.0.1:6379> lrange a1 0 -1
1) "1"
2) "1"

好了,a 新插入的数据马上被移动到了 a1 中。

删除元素

其实上面 POP 相关的操作也是一种删除操作,但它们都是定死的只能从某一个方向去把数据弹出来。真正的删除肯定是我们去随便删除指定的数据嘛,先来看看 LREM 这个命令。

LREM 是根据提供的值,去删除元素。注意,不是下标,不是下标,不是下标。

代码语言:javascript复制
127.0.0.1:6379> lpush a 1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3
(integer) 17
127.0.0.1:6379> lrem a 4 1
(integer) 3
127.0.0.1:6379> lrem a 2 2
(integer) 2
127.0.0.1:6379> lrange a 0 -1
 1) "3"
 2) "7"
 3) "6"
 4) "5"
 5) "4"
 6) "3"
 7) "7"
 8) "6"
 9) "5"
10) "4"
11) "3"
12) "2"

第一次,我们删除了 4 这个元素,后面的 1 表示只删除 1 个 4 。第二次删除了 2 个 2 。这个例子不太好,因为我的数据也是数字,但是,大家要是能一眼看明白这段代码的意思,就能很清楚地明白 LREM 是删除值而不是下标的意思了。

只保留指定区间元素

除了删除指定的值之外,我们还可以按范围删除,或者说是按范围保留。

代码语言:javascript复制
127.0.0.1:6379> ltrim a 2 5
OK
127.0.0.1:6379> lrange a 0 -1
1) "6"
2) "5"
3) "4"
4) "3"

LTRIM key 2 5 的意思就是只保留下标 2 到 5 的数据,或者反过来说是把 0、1 和 5 之后的数据都删除了。

删除指定下标元素

Redis 中没有提供按指定下标删除 List 数据的命令,那要实现删除指定下标的元素要怎么弄呢?

代码语言:javascript复制
127.0.0.1:6379> lset a 1 -1
OK
127.0.0.1:6379> lrem a 1 -1
(integer) 1
127.0.0.1:6379> lrange a 0 -1
1) "6"
2) "4"
3) "3"

使用 LSET 和 LREM 配合起来,先将要删除的下标位置的元素改成一个固定的值,比如我这里用的 -1 ,或者你也可以用 del 、invalid 之类的字符串。然后使用 LREM 去批量删除它们就好了。

总结

奇怪的小知识又增加了吧?说实话,之前我还真不知道 LMOVE 和 BLPOP 这一类的东西,既然知道了,那么下回做队列的时候咱也可以试试了。List 相关的操作命令就是这些,其实也不难,你可以把它当成是一个可以方便地实现队列和栈的数组,其实之前我也一直就只是把它当普通队列来用的,更复杂的优先和延时队列在 Redis 中还有别的数据结构来实现,后面我们也会学习到。

0 人点赞