【Redis】List类型
文章目录
- List的特点介绍
- lpush,lpushx,rpush,rpushx命令
- lrange命令
- lpop和rpop
- lindex命令
- linsert命令
- llen命令
- lrem 命令
- ltrim命令
- lset命令
- 阻塞版本的命令
- blpop和brpop
- 命令小结
- list的内部编码
- List的应用场景
List的特点介绍
列表相当于一个数组或者顺序表,但是不是一个简单的数组,而是接近双端队列(deque)的结构。
对于双端队列来说,头插头删和尾插尾删的效率都很高,时间复杂度是O(1).
对于普通的数组来说,只有尾插尾删的效率高一点,但是头插和头删会涉及到内存的大量操作,效率不高。
列表类型的特点:
-
- 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,例如要获取上图第 5 个元素,可以执⾏ lindex user:1:messages 4 或者倒数第 1 个元素,lindex
user:1:messages -1 就可以得到元素 e。
- 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,例如要获取上图第 5 个元素,可以执⾏ lindex user:1:messages 4 或者倒数第 1 个元素,lindex
这里的有序两个字,要根据上下文区分。
很明显,列表的有序指的是元素按照一定的顺序排列的。
但是实际中的有序,可能是升序和降序,也有可能是按照某个条件进行的有序。
举个例子:假如面试官问你堆栈的区别,你这时候就先不要着急回答。
而是先反问面试官,您这里说的堆栈,是数据结构的堆栈呢,还是操作系统的堆栈呢,还是JVM中的堆栈呢?亦或者是进程地址空间的堆栈呢?
还有一个同步,同步是指通过加锁来保证线程安全的同步呢,还是IO中的同步和异步(这里的异步其实就是并发)中的同步呢?
-
- 区分获取和删除的区别,例如 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删除,这个操作会导致列表的⻓度从 5 变成 4;但是执⾏ lindex 4 只会获取元素,但列表⻓度是不会变化的。
-
- 列表中的元素是允许重复的。
deque的底层结构介绍如下:
注意区分lindex和lrem,即获取元素和删除元素的区别。
lindex 能获取到元素的值,lrem也能返回被删除的元素的值。
因为当前的list的头和尾都能高效地插入和删除元素,所以当前的List可以用来作栈和队列使用。
后端开发中,栈用的比较少,但是队列就非常重要。比如使用队列实现生产消费者模型。
还有实现消息队列。
lpush,lpushx,rpush,rpushx命令
将一个或多个元素头插到list中
lpush key element [element …]
比如:lpush key 1 2 3 4 ,插入完成后,列表的元素是4 3 2 1
返回值:插入后的list的长度。
如果key已经存在,且key对应的value不是list类型,则lpush命令会报错。
Redis中所有类型的容器都是类似效果。
时间复杂度O(1).
lpushx命令:
在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回
lpushx key element [element … ]
时间复杂度O(1)
rpush和rpushx,其实就是尾插入,其他的完全相同。
操作的时间复杂度也是O(1)
lrange命令
获取key对应的[start stop]区间的元素,左闭右闭。
lrange key start stop
时间复杂度O(n)
如果给出的下标超出范围了,在Redis中的做法是:
直接尽可能给到区间内的元素,如果下标非法,就尽可能获取对应的内容。
这就是所谓的 ”鲁棒性“:你对我越粗鲁,我表现的越棒。(其实就是容错性强了)
然而在C++中,下标超出范围,这是一个未定义行为:
1.可能导致程序崩溃
2.可能得到不合法的数据
3.可能得到看起来合法,但是是错误的数据。
4.可能得到一个恰好符合结果的数据
就像是开盲盒一样。
缺点:程序员不能第一时间发现问题,可能会出现连锁反应。
优点:效率是最高的。相比其他编程语言,比如java,对下标超范围行为会多了一个合法性验证,然后抛异常,这就导致多了一些动作。
但是人家java这样做也有优点,就是对程序员来说能更快发现问题,也就能更高效开发代码。
所以就产生了两个问题:
是机器跑得快重要呢,还是程序员开发代码更快重要呢?
肯定是程序员开发代码重要,因为涉及到了利益问题,程序员如果开发的慢了,可能要加班修bug开发代码,甚至如果搞砸了,可能要丢失年终奖。但是对程序员来说,机器跑的快不快跟我有啥关系呢,跑的慢的话,让老板多搞两台机器过来不就行了嘛。
lpop和rpop
lpop相当于头删
lpop key
返回值:返回删除后的元素,如果列表没有元素了,则返回nil
时间复杂度O(1)
rpop相当于尾删
rpop key
返回值:返回删除后的元素,如果列表没有元素了,则返回nil
时间复杂度O(1)
从Redis 6.2版本中,新增了一个count选项(当前我用的是Redis 5,暂不考虑)
lpop key [count]
表明要头删几次
lindex命令
给定下标,获取到指定下标的元素
lindex key index
支持负数下标,-1表示倒数第一个了。依次类推。
如果下标非法,返回nil
时间复杂度O(n),因为在redis的list列表不是一个简单的数组,所以不能理解成O(1)的复杂度。
linsert命令
在指定位置插入元素
linsert key <before | after> pivot element
在指定的元素pivot之前/之后,插入元素element
如果指定的元素pivot在列表中存在多个,则在从左往右搜索到的第一个pivot之前/之后插入。
llen命令
获取key对应的list的长度
llen key
lrem 命令
rem就是remove命令的缩写,就是删除命令。
lrem key count element
count是要删除的个数,element是删除的值
官方文档给定的解释如下
意思是:
如果count > 0,则是从头到尾开始删除等于element的元素,删count个。
如果count < 0, 则是从尾到头开始删除等于element的元素,删count个。
如果count = 0, 则是删除所有等于element的元素。
lrem返回值是被删除的元素个数。
ltrim命令
该命令是保留key对应的list的[start stop]区间内的元素(同样是左闭右闭),区间外的元素都删除。
ltrim key start stop
时间复杂度O(1),也可以理解为O(N),这个N是要删除的元素个数。
lset命令
把key对应的list列表中的index下标的元素修改成element
lset key index element
时间复杂度O(N),如果是修改头或者尾,则时间复杂度是O(1)
如果给的下标非法,则直接报错。
相比于lindex命令不同的是,lindex命令对于非法的下标不报错,而是尽可能满足。。。
阻塞版本的命令
blpop和brpop
blpop key [key …] timeout
意思是可以同时对多个key的list进行头删
返回值是成功执行blpop命令的key和其删除的元素
brpop key [key …] timeout
同理
这个timeout(必选项,单位是秒)是设置阻塞的时间,下面会详细介绍
在list中存在元素的情况下,blpop和brpop命令和lpop,rpop命令作用完全相同。
但是如果list中没有元素,则blpop和brpop会产生阻塞。
这里的阻塞很好理解,就是生产消费者模型中的阻塞。
这里并不是无休止地阻塞,阻塞时间由timeout决定,在阻塞期间,Redis可以执行其他命令(这是经过特殊处理的,毕竟怎么可能让两个命令阻塞住Redis处理命令时的单线程模型呢,如果阻塞住了, 其他客户端发来的命令请求就得不到执行了。
注意事项:
- 1.如果命令设置了多个key,那么会从左到右遍历这些key,一旦有其中一个key对应的list的元素就绪了,就会马上头删该key对应的list,然后立即返回。
- 2.如果多个客户端同时对一个key进行blpop/brpop,则最先执行命令的客户端会得到删除后的元素
所以返回值是一个二元组,一方面是告诉我这个数据是来自哪个key,一方面是告诉我这个数据是什么。
这种情况就是key不存在,所以阻塞住了,一旦key就绪了,就立即删除返回。
这个15.17s就是阻塞的时长。
命令小结
list的内部编码
其实前面的文章讲过了。
List的应用场景
Redis 阻塞消息队列模型
分频道阻塞消息队列模型