Redis数据类型之列表List
想学习更多Redis相关知识,请点击右侧链接查看Redis学习笔记:点我查看
一、Redis列表类型介绍
在正式讲解Redis列表时,我们需要对“列表”这一概念做一个详细说明:
列表是一种可以存储多个数据的结构,
在内存中,列表的存储主要有两种方式:一种是通过数组实现的列表(例如:Java中的ArrayList),另一种是通过链表实现的列表(例如:Java中的LinkedList),两种方式各有优缺点:
优点 | 缺点 | |
---|---|---|
数组 | 数组元素在内存中是连续存储的,存储结构简单,访问速度块 | 1. 数组大小需要在创建过程中指定,不能动态调整 2. 插入和删除操作效率较低 3. 需要连续的内存空间 |
链表 | 1. 可以动态调整链表大小,内存利用率较高 2. 插入和删除操作较为灵活 3. 元素可以分布在内存的任何位置,不需要连续的内存空间 |
1. 访问元素较慢,查找元素需要从头遍历 2. 由于链表元素在内存中分布不连续,访问速度较慢 3. 每个元素需要额外的指针存储前一个或后一个节点,内存开销增加 |
首先,Redis列表(List)符合其本身和列表的基本特征:一个键(key)对应多个值(value)。
其次,Redis的列表是通过链表实现的,这也就意味着Redis添加元素效率非常高,并且Redis添加元素的主要方式是向列表的头部或者尾部添加元素。
由于Redis列表本身是一个链表,它的缺点在于访问某个元素并没有这么快。因此,Redis列表适合于存储较少的数据。如果需要存储大量数据,并且需要快速访问,可以使用Sorted Set(ZSet)解决这一问题。
应用场景:
1. 记录用户在社交网络最新发送的内容,例如:微信公众号文章推送
2. 进程间通信,使用生产者-消费者模式。生产者将数据存放到列表,消费者取出数据并执行相应的处理操作。
二、Redis列表类型常用命令
1. (列表头部)从左向右依次将元素存入列表中
1 |
|
假设向当前列表list的左侧添加三个元素e1、e2和e3,这个存储过程如下图所示:
LPUSH命令执行成功默认返回数据存入到列表后列表中元素的总数量,以下是LPUSH命令的使用演示:
2. (列表尾部)从右向左依次将元素存入到列表中:
1 |
|
假设向当前列表list的右侧添加两个元素e1、e2,这个存储过程如下图所示:
LPUSH命令执行成功默认返回数据存入到列表后列表中元素的总数量,以下是RPUSH命令的使用演示:
3. 从左向右获取列表指定范围的元素:
1 |
|
和字符串类型类似,Redis列表中每一个元素也有索引。假设当前Redis列表长度是 list.length
:
从左到右计算出的索引值范围是$[0, list.length)$,索引最大值是 list.length - 1
;
Redis也可以从右向左计算索引,默认最右侧的索引值为-1,到最左侧索引值为$-list.length$,右侧可以得出的索引值范围是$[-list.length, -1]$。
假设当前key对应的列表中有8个元素,以下是对索引值范围的分析图:
我们在使用LRANGE命令时,start和stop可使用索引范围是$[0, list.length)$或者$[-list.length, -1]$,两个范围内的值可以混用。例如:start位置是0,stop表示列表最末尾的元素就可以使用-1表示,使用LRANGE获取整个列表的内容的执行命令是 LRNAGE key 0 -1
。
以下是LRANGE命令的使用:
4. 获取当前列表长度:
1 |
|
5. 从左侧弹出一个元素
1 |
|
默认返回的就是当前弹出元素的值:
以下是LPOP执行过程动画(橘黄色为弹出的元素):
6. 从右侧弹出一个元素
1 |
|
默认返回就是当前弹出元素的值:
以下是RPOP执行过程动画(橘黄色为弹出的元素):
7. 根据索引值获取列表指定元素(默认从0开始):
1 |
|
这里的index就是列表的索引值,它的范围和我们在前面探讨列表的索引值范围一样。假设列表的长度是$list.length$,那么从左到右索引范围:$[0, list.length-1)$,从右向左计算得出的范围是$[-list.length, -1]$。
以下是LINDEX命令的使用:
8. 删除列表中count个值为element的元素:
1 |
|
这里count分成如下几种情况:
- count > 0:从头到尾删除count个值为element的元素;
- count = 0:删除列表中所有值为element的元素;
- count < 0:从尾到头删除countge值尾element的元素;
如果当前的key不存在则视为空列表,此时执行LREM命令返回结果是0。
以下是LREM命令的使用:
9. 截取指定范围的值后,再赋值给当前的key:
1 |
|
这里start和stop的索引值范围和前面讨论的范围相同,需要保证start和stop在索引值范围内。
如果stop的值比当前列表长度大,那么在执行LTRIM操作时,得到的结果仍然是当前列表的尾部位置,即$[start, list.length)$。
以下是LTRIM命令的使用:
10. 当前列表右侧弹出一个元素,于此同时从另一个列表的左侧存入值:
1 |
|
假设source列表中有如下元素:e1、e2、e3;destination列表有如下元素:e4、e5。
执行RPOPLPUSH后,source列表尾部元素e3弹出,然后马上在destination列表头部存入,此时得到的结果是:
- source列表:e1、e2;
- destination列表:e3、e4、e5;
以下是RPOPLPUSH命令演示:
下图演示了RPOPLPUSH命令如何将e3从source列表存入到destination列表中:
11. 为当前列表指定位置设置新值:
1 |
|
12. 在列表某个已有值的前后再添加具体值:
1 |
|
返回结果是当前列表的长度。
如果当前key对应的是空列表,那么此命令不会执行任何操作。
如果key存在,但是列表中不存在pivot的值,此时系统会提示错误信息。
以下是LINSERT命令的使用:
三、Redis列表类型的注意事项
1. 访问列表的头部和尾部的时间复杂度是$O(1)$,这也就意味着访问这两个位置的效率非常高。然而,在列表内部需要操作元素的命令时间复杂度一般是$O(n)$,例如:
- LINDEX
- LINSERT
- LSET
2. Redis列表最多可以存储$2^{32}-1$个元素:
3. 如果列表为空,Redis会自动删除对应的key;如果执行添加操作时,如果key不存在,此时Redis会创建一个列表并向里面添加元素。