Redis数据结构之list列表

一.list列表

列表相当于数组或者顺序表

它里面的元素是有序的,也就是可以通过下标进行访问。这里的有序的含义是要根据上下文区分的,有的时候,有序指的是升序/降序,有的时候有序指的是顺序很关键,俩个元素交换后就不是同一个集合了。这里的list就是第二种,对应的下标有对应的值。

所以说,同样的一个词,怎么解释要看上下文。就好比栈/堆,是数据结构?还是操作系统的?还是JVM的?

也比如同步,是同步互斥的同步?还是同步异步的同步?

这里的list并不是一个简单的数组,它的底层接近于双端队列deque:

如上图,这个list支持头尾高效的删除插入元素,所以可以将list当成一个栈/队列来使用。

redis有一个典型的应用场景,就是作为消息队列,最早的时候就是通过list类型,只不过后来redis又提供了stream类型

list列表中的元素是允许重复的,但是像hash这样的类型,field是不能够重复的(因为它获取value就是根据field,如果重复了就不知道获取哪个value了;而list就是根据下标进行访问)。

二.相关命令

1.lpush

一次可以插入一个或多个元素,而且是按照顺序依次进行头插,所以全部插入完毕后,最后写下的匀速就是在list的最前边

时间复杂度:O(1)

返回值:list的长度

如果key已经存在,而且对应的value类型不是list,就会报错

2.lrange查看对应的list中指定范围的元素

lrange key start stop 注意,这里的区间是左右都闭

下标支持负数

如果给定的区间非法,比如超出了范围,他就会尽可能地获取到对应的内容如下:

这体现了程序的容错能力,也体现了鲁棒性(你对我越粗鲁,我就表现得越棒)

3.lpushx

与lpush不同的是,当指定的list存在时(也就是key存在),才能将元素放进去,如果list不存在,就直接返回

list2先前没有创建,所以push不进去

4.rpush

其实lpush是left push,所以rpush就是right push,也就是尾插啦

5.lpop rpop

左删和右删

返回值是删除的元素

时间复杂度都是o(1)

小结一下,redis中的list是一个双端队列,俩头插入/删除的时间复杂度都是O(1),搭配使用rpush和lpop就相当于队列(尾进头出),搭配使用rpush和rpop就相当于栈(尾进尾出)。

6.lindex

lindex key index 给定下标,获取对应的元素

时间复杂度是O(N),其实这里的N指的是list中的元素个数,当元素很少时,就能看做O(1)

下标非法就返回nil

7.linsert

linsert key before/after pivot element 在指定元素pivot之前/后插入元素element

返回值是插入之后的list的长度

8.llen

返回list的长度

时间复杂度:O(1)

9.lrem

lrem key count element 指定删除对应的key中的count个element,如果不够count个,那就能删几个删几个

count>0 那就从head开始删

count<0那就从tail开始删

count=0那就删除所有的element

10.ltrim

ltrim key start stop 保留start到stop之间的元素

11.lset

lset key index element 根据下标,修改元素

lindex对于下标的越界访问能够很好的处理,直接返回nil,但是对于lset来说,则会报错

时间复杂度:O(1)

12.阻塞版本

首先讲一下什么是阻塞:就是当前的线程不走了,代码不继续执行了,会在满足一定的条件之后被唤醒。

阻塞版本的头尾删除是:blpop brpop

当list不为空时,blpop和brpop就和lpop rpop效果完全一样;但当为空时,b版本的就会阻塞,直到再次插入元素。

咱们讲过阻塞队列(BlockingQueue)。多线程的时候,讲过一个生产者消费者模型,就是使用队列作为交易场所,期望这个队列右两个特性:1.线程安全2.阻塞->如果队列为空,尝试出队列就产生阻塞,如果队列为满,尝试入队列,就产生阻塞,直到队列不满解除阻塞。

redis中的list也相当于阻塞队列一样。线程安全是通过单线程模型支持的,阻塞则只支持队列为空的条件,不支持队列为满。

阻塞版本会根据timeout阻塞一段时间,但是阻塞期间可以执行其他指令。可以显示设置阻塞时间,超过后就自动返回。

blpop key [key......] timeout

先看指定一个key的版本,打开两个客户端

第一个窗口中先执行blpop,设定时间为100秒

然后第二个窗口里个该list设值

在回到第一个窗口,发现blpop出现了结果,返回值是list的名称加删除的结果加使用的时间

如果针对多个list进行操作,那么他也会尝试获取,但要是都没有元素,那就是最先插入元素的客户端会得到弹出的元素,但如果都有元素,那就是返回最先执行的客户端中的元素

先看都为空的情况:

再看都为满的情况:

如上,最先执行blpop的是lt2,所以返回lt2的

这俩个阻塞命令的用途主要就是作为消息队列,可以一定程度慢则消息队列这样的要求,但是整体来说还是有局限的

三.内部编码

列表的内部编码有两种:

1.ziplist压缩列表,当列表的元素个数小于list-ziplist-entries的配置时(默认是512个)同时列表中每一个元素的长度都小于list-max-ziplist-value的设置是,就会选用ziplist的内部编码方式来减少内存消耗

2.linkedlist,除上述之外都是linkedlist

但现在已经不适用这种方式了,而是直接使用quicklist,quicklist相当于是列表和压缩列表的结合体,就是每一个压缩列表都不要太大,同时再把多个压缩列表用链式结构连起来

四.list应用场景

1.list作为数组

list作为数组存储多个元素

在MySQL中,如下表示学生和班级的信息

如果使用redis,就可以像下面一样:

也就是每一个学生/班级对应一个list,每一个list存放的都是学生信息/班级信息。

2.redis作为消息队列

一个列表,多人获取:

谁先执行brpop这个命令,谁就先拿到新来的元素。

像这样的设定就能够达到"轮询式"的效果:假设消费者执行的顺序是1,2,3,当新元素到达后,消费者1就先拿到元素并且退出,如果消费者1还想再钠元素,就要重新执行brpop命令;此时再来一个新元素就是消费者2拿到元素,它想再拿到元素就得重新执行brpop命令;再来一个新元素就是消费者3拿到了~~,就好像食堂排队打饭一样

多个列表,多人获取:

多个列表/频道,这种场景非常常见,就比如抖音/快手~~

有一个通道是传输视频数据,还有一个通道传输弹幕,还有一个频道传输点赞、转发、收藏数据,还有一个频道传输评论数据

搞成多个频道,就可以在某种数据发生问题时,不会对其他频道的数据产生影响(解耦合)

就比如上图中,brpop key1 key2的key1阻塞了,但是对key2不会有影响,还能够拿到数据,堆key3也不会有影响,最大限度地减少了阻塞带来的风险

3.微博timeline

每一个用户都有属于自己的timeline(微博列表),现在需要分页展示文章列表,就可以使用到list。因为list不单是有许多,同时支持按照索引范围获取元素

首先,每一篇微博使用hash结构存储(一篇微博对应一个hash,一个field对应一个value)

其次,要向用户添加微博,user:<uid>:mblogs就是微博的键

最后,分页获取用户的timeline

但是当前一页中要显示多少数据,不确定,就可能会到这上面的循环次数比较多,从而会触发很多次hgetall,也就是很多次网络请求。此时可以考虑使用pipeline(流水线)模式批量提交用户命令,或者微博不采用hash进行存储,而是用字符串形式,然后用mget来获取(但一般不这样)

pipeline:流水线/管道,多个redis命令合并为一个网络请求进行通信,大大降低了客户端和服务器之间的交互次数

相关推荐
月光水岸New1 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6751 小时前
数据库基础1
数据库
我爱松子鱼1 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser3 小时前
【SQL】多表查询案例
数据库·sql
Galeoto3 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto3 小时前
详解Redis在Centos上的安装
redis·centos
人间打气筒(Ada)3 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231113 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
喝醉酒的小白3 小时前
PostgreSQL:更新字段慢
数据库·postgresql