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

文章目录
    分类:存储运维 标签:Redis

    Redis基础学习:List相关操作

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

    添加元素

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

    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 表示的反向取数据,你可以把这种下标看成是一种 环形 取数据的方式。

    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 命令,它是根据指定的下标进行数据修改的。

    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 不存在,就无法修改,并且会分别报出不同的错误。

    根据指定元素值插入

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

    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 ,分别代表根据指定值,从它的前面或者后面插入数据。

    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 命令,就可以获取到指定下标的元素数据。

    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 获取到指定元素的下标。

    127.0.0.1:6379> lpos a -33
    (integer) 5

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

    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 元素。

    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 参数。

    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 也可以组合使用。

    127.0.0.1:6379> lpos a -33 count 0 rank 2
    1) (integer) 7
    2) (integer) 11
    3) (integer) 13

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

    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 中删除它。

    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 。

    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 ,当队列空的时候,它会阻塞到这里,直到获取到下一条数据。

    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 的话,就会一直等待。

    127.0.0.1:6379> lpush a1 1
    (integer) 1

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

    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 类似的,所以大家直接看下代码就好了。

    127.0.0.1:6379> blmove a a1 left left 0

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

    127.0.0.1:6379> lpush a1 1
    (integer) 1
    127.0.0.1:6379> lpush a 1
    (integer) 1

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

    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 是根据提供的值,去删除元素。注意,不是下标,不是下标,不是下标。

    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 是删除值而不是下标的意思了。

    只保留指定区间元素

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

    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 数据的命令,那要实现删除指定下标的元素要怎么弄呢?

    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 中还有别的数据结构来实现,后面我们也会学习到。

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/GI5aMwkrulJoCXQC_H_Pfw

    B站视频地址:https://www.bilibili.com/video/BV1Wo4y1x7ns

    微信视频地址:https://mp.weixin.qq.com/s/z45t9-h9I598oXlFm9L2LQ

    搜索
    关注