列表
- 列表的元素是有序的,这意味着通过索引下标获取某个元素或者某个范围内的元素列表
- 列表中的元素可以是重复的
命令
命令 | 描述 | 返回值 |
---|---|---|
lpush | lpush k-name value [value …]–将一个或者多个值推入列表的右端 | 执行 lpush 命令后,当前列表的长度 |
rpush | rpush k-name value [value …]–将一个或者多个值推入列表的左端 | 执行 rpush命令后,当前列表的长度 |
lpop | lpop k-name --移除并且返回列表最右端的元素 | 被移除的元素,当列表 key 不存在时,返回 nil |
rpop | rpop k-name–移除并且返回列表最左端的元素 | 被移除的元素,当列表 key 不存在时,返回 nil |
lpushx | lpushx k-name value [value …]–将一个或者多个值推入已经存在了边列表的右端 | 执行 lpushx 命令后,当前列表的长度。当且仅当列表存在时,此命令有效。如果列表不存在,返回长度0 |
rpushx | rpushx k-name value [value …]–将一个或者多个值推入已经存在了边列表的左端 | 执行 lpushx 命令后,当前列表的长度。当且仅当列表存在时,此命令有效。如果列表不存在,返回长度0 |
blpop | blpop k-name [k-name …]timeout | 在超时时间内等待元素弹出 |
brpop | brpop k-name [k-name …]timeout | 在超时时间内等待元素弹出 |
rpoplpush | rpoplpush source-key dest-key –从s-key列表种弹出位于最右端的元素并推入dest-key中 | 被弹出的元素 |
rpoplpush | rpoplpush source-key dest-key timeout–在timeout内从s-key列表种弹出位于最右端的元素并推入dest-key中 | 被弹出的元素。 |
lindex | lindex k-name offset –从列表种取出索引位offset的元素 | 0为起始索引,-1为结束索引。如果指定索引值不在列表的区间范围内,返回 nil |
lrange | lrange k-name start end–返回索引[start, end]之内的元素 | 0为起始索引,-1为结束索引。如果指定索引值不在列表的区间范围内,什么也不返回 |
ltrim | ltrim k-name start end–只保留索引[start, end]内的元素,其他的删除 | 0为起始索引,-1为结束索引 |
llen | llen k-name –返回列表的长度 | 如果是空列表,返回0.如果不是列表返回错误 |
lset | lset k-name index new-value –设置索引index处的值位new-value. | 操作成功返回ok, 当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。 |
添加
lpush:从左边添加元素
只用
- 将一个或多个值插入到列表头部
- 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作
- 当 key 存在但不是列表类型时,返回一个错误。
注意:在Redis 2.4版本以前的 LPUSH 命令,都只接受单个 value 值。
语法
redis 127.0.0.1:6379> LPUSH KEY_NAME VALUE1.. VALUEN
返回值
执行 LPUSH 命令后,列表的长度。
实例
127.0.0.1:6379> LPUSH list1 "foo"
(integer) 1
127.0.0.1:6379> LPUSH list1 "bar"
(integer) 2
127.0.0.1:6379> LRANGE list1 0 -1
1) "bar"
2) "foo"
rpush:从右边添加元素
作用
- Redis Rpush 命令用于将一个或多个值插入到列表的尾部(最右边)。
- 如果列表不存在,一个空列表会被创建并执行 RPUSH 操作。
- 当列表存在但不是列表类型时,返回一个错误。
注意:在 Redis 2.4 版本以前的 RPUSH 命令,都只接受单个 value 值。
语法
redis 127.0.0.1:6379> RPUSH KEY_NAME VALUE1..VALUEN
返回值
执行 RPUSH 操作后,列表的长度。
实例
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
redis 127.0.0.1:6379> RPUSH mylist "foo"
(integer) 2
redis 127.0.0.1:6379> RPUSH mylist "bar"
(integer) 3
redis 127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "foo"
3) "bar"
linsert
作用
- Redis Linsert 命令用于在列表的元素前或者后插入元素。
- 当指定元素不存在于列表中时,不执行任何操作。
- 当列表不存在时,被视为空列表,不执行任何操作。
- 如果 key 不是列表类型,返回一个错误。
语法
LINSERT key BEFORE|AFTER pivot value
将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
返回值
- 如果命令执行成功,返回插入操作完成之后,列表的长度。
- 如果没有找到指定元素 ,返回 -1 。
- 如果 key 不存在或为空列表,返回 0 。
实例
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
redis>
查找
lrange:获取指定范围内的元素列表
作用
获取指定区间内的元素
索引下标有两个特点:
- 索引下标从左到右分别是0到N-1,从右到左是-1到-N
- lrange的end选项包含了自身
语法
redis 127.0.0.1:6379> LRANGE key start end
返回值
一个列表,包含指定区间内的元素。
实例
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3
redis> LRANGE mylist 0 0
1) "one"
redis> LRANGE mylist -3 2
1) "one"
2) "two"
3) "three"
redis> LRANGE mylist -100 100
1) "one"
2) "two"
3) "three"
redis> LRANGE mylist 5 10
(empty list or set)
redis>
lindex:获取指定下标的元素
- 用于通过索引获取列表中的元素
- 可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
语法
lindex key index_postion
返回值
- 列表中下标为指定索引值的元素。
- 如果指定索引值不在列表的区间范围内,返回 nil 。
实例
redis 127.0.0.1:6379> LPUSH mylist "World"
(integer) 1
redis 127.0.0.1:6379> LPUSH mylist "Hello"
(integer) 2
redis 127.0.0.1:6379> LINDEX mylist 0
"Hello"
redis 127.0.0.1:6379> LINDEX mylist -1
"World"
redis 127.0.0.1:6379> LINDEX mylist 3 # index不在 mylist 的区间范围内
(nil)
llen:获取列表长度
作用
- 用于返回列表的长度
- 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0
- 如果 key 不是列表类型,返回一个错误。
语法
llen key
返回值
- 列表的长度
实例
redis 127.0.0.1:6379> RPUSH list1 "foo"
(integer) 1
redis 127.0.0.1:6379> RPUSH list1 "bar"
(integer) 2
redis 127.0.0.1:6379> LLEN list1
(integer) 2
删除
lpop:从左边弹出元素
作用
- 移除并返回列表的第一个元素。
语法
lpop key
返回值
- 列表的第一个元素。
- 当列表 key 不存在时,返回 nil 。
实例
redis 127.0.0.1:6379> RPUSH list1 "foo"
(integer) 1
redis 127.0.0.1:6379> RPUSH list1 "bar"
(integer) 2
redis 127.0.0.1:6379> LPOP list1
"foo"
rpop:从右边弹出元素
作用
- 用于移除列表的最后一个元素
- 返回值为移除的元素
语法
rpop key
返回值
- 被移除的元素。
- 当列表不存在时,返回 nil 。
实例
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3
redis> RPOP mylist
"three"
redis> LRANGE mylist 0 -1
1) "one"
2) "two"
redis>
lrem:删除指定元素
作用
Redis Lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
COUNT 的值可以是以下几种:
- count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
- count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
- count = 0 : 移除表中所有与 VALUE 相等的值。
语法
redis 127.0.0.1:6379> LREM key count VALUE
返回值
- 被移除元素的数量。
- 列表不存在时返回 0 。
实例
比如向列表元素为:a a a a a java b a
,下面操作将从列表左边开始删除4个为a的元素
> lream listkey 4 a
(integer) 4
> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
4) "a"
ltrim:按照索引范围修建列表
作用
- Redis Ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
语法
ltrim key start stop
返回值
- 命令执行成功时,返回 ok 。
实例
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2
redis 127.0.0.1:6379> RPUSH mylist "foo"
(integer) 3
redis 127.0.0.1:6379> RPUSH mylist "bar"
(integer) 4
redis 127.0.0.1:6379> LTRIM mylist 1 -1 # 保留第一个元素
OK
redis 127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "foo"
3) "bar"
修改
lset:修改指定下标的元素
作用
- 将列表 key 下标为 index 的元素的值设置为 value
- 当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
时间复杂度
- 对头元素或尾元素进行 LSET 操作,复杂度为 O(1)。
- 其他情况下,为 O(N), N 为列表的长度。
实例
# 对空列表(key 不存在)进行 LSET
redis> EXISTS list
(integer) 0
redis> LSET list 0 item
(error) ERR no such key
# 对非空列表进行 LSET
redis> LPUSH job "cook food"
(integer) 1
redis> LRANGE job 0 0
1) "cook food"
redis> LSET job 0 "play game" # 将第一个元素改为"play game"
OK
redis> LRANGE job 0 0
1) "play game"
# index 超出范围
redis> LLEN list # 列表长度为 1
(integer) 1
redis> LSET list 3 'out of range'
(error) ERR index out of range
阻塞操作
blpop
作用
- 移出并获取列表的第一个元素
- 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
语法
blpop key [key ...] timeout
返回值
- 如果列表为空,返回一个 nil 。
- 否则,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
实例
> blpop list:test 3 // 客户端最多阻塞3s后返回
(nil)
(3.10)
> blpop list:test 0 // 客户端一直阻塞直到得到了元素
1) "list:test"
2) "ele1"
注意
(1)如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回;
127.0.0.1:6379> brpop list:1 list:2 list:3 0
..阻塞..
此时另一个客户端分别向list:2和list:3插入元素:
client-lpush> lpush list:2 element2
(integer) 1
client-lpush> lpush list:3 element3
(integer) 1
客户端会立即返回list:2中的element2,因为list:2最先有可以弹出的元素:
127.0.0.1:6379> brpop list:1 list:2 list:3 0
1) "list:2"
2) "element2_1"
(2)如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值;
客户端1:
client-1> brpop list:test 0
...阻塞...
客户端2:
client-2> brpop list:test 0
...阻塞...
客户端3:
client-3> brpop list:test 0
...阻塞...
此时另一个客户端lpush一个元素到list:test列表中:
client-lpush> lpush list:test element
(integer) 1
那么客户端1最会获取到元素,因为客户端1最先执行brpop,而客户端2 和客户端3继续阻塞:
client> brpop list:test 0
1) "list:test"
2) "element"
brpop
和上面的除了弹出方向不一样,使用方式一样
内部编码
列表的内部编码有两种
- ziplist(压缩列表)
- 当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使
- linkedlist(链表)
- 当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现。
下面的示例演示了列表类型的内部编码,以及相应的变化。
1)当元素个数较少且没有大元素时,内部编码为ziplist:
127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist
2)当元素个数超过512个,内部编码变为linkedlist:
127.0.0.1:6379> rpush listkey e4 e5 ...忽略... e512 e513
(integer) 513
127.0.0.1:6379> object encoding listkey
"linkedlist"
3)或者当某个元素超过64字节,内部编码也会变为linkedlist:
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte............... ................."
(integer) 4
127.0.0.1:6379> object encoding listkey
"linkedlist"
其他:
- Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节 点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一 种更为优秀的内部编码实现
- 它的设计原理可以参考Redis的另一个作者 Matt Stancliff的博客:https://matt.sh/redis-quicklist
使用场景
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
消息队列
如下图所示,Redis的lpush+brpop命令组合即可实现阻塞队列,
- 生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令 阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
- 如果消费者在取出元素之后立即“崩溃”,由于该消息已经被取出但是没有被正常处理,此时可以认为该消息已经丢失了,可能会导致业务数据丢失或者业务数据不一致等,此时我们了可以用rpoplpush命令使得消费者在主消息队列中取出元素之后再将其插入到一个“备份队列”中,直到消费者完成正常的逻辑处理之后再将消息从备份中删除,这样可以提供一个守护的线程。当备份中的消息过期之后,可以将消息重新放回到主消息队列中,以便其他消息可以几区去使用
存储任务信息
列表可以用来存储任务信息,最近浏览过的文章或者常用联系人信息,离用户最近的五家餐厅等。类似双向链表
每个用户都有属于自己的文章列表,现在需要分页展示文章列表。此时可以使用列表,因为列表不但是有序的,而且支持索引范围获取元素
1)每篇文章使用哈希结构存储,例如每篇文章有3个属性title、 timestamp、content:
hmset acticle:1 title xx timestamp 1476536196 content xxxx
...
hmset acticle:k title yy timestamp 1476512536 content yyyy
...
2)向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键:
lpush user:1:acticles article:1 article3
...
lpush user:k:acticles article:5
...
3)分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章:
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
使用列表类型保存和获取文章列表会存在两个问题。
- 第一,如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用 Pipeline批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。
- 第二,分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此 时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现, 它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。
其他
- 关注列表、粉丝列表
- 最新列表:获取前N个最新登录的用户列表
- 抢购:使用redis的原子操作来实现这个“单线程”,首先我们把库存存在good_things:1这个列表中,如果有10件库存,就往列表中push10个数。抢购开始后,每来一个用户,就或从good_things:1中pop一个数,表示抢购成功,列表为空时,表示商品已经被抢完。pop操作是原子性的,此时如果有很多用户同时到达、也是一次可行的
总结:
1、lpop和rpop命令有对应的阻塞版本:BLPOP和BRPOP
- 它们之间的不同在于当列表为空时,阻塞版本会将客户端阻塞,我们必须在这些阻塞版本的命令中指定一个以秒为单位的超时时间,表示最长等待几秒。
- 当超时时间为零时,表示永久等待,可以用于任务调度
2、redis在内部使用quicklist存储列表对象。有两个配置选项可以调整列表对象的存储逻辑:
- list-max-ziplist-size:一个列表条目中一个内部节点的最大大小[quicklist的每个节点都是一个ziplist]。使用默认值即可
- list-compress-depth:该参数表示quicklist两端不被压缩的节点的个数
3、在redis里面,多个命令原子的执行指的是,在这些命令正在读取或者修改数据的时候,其他客户端不能读取或者修改相同的数据
4、对于阻塞弹出命令和弹出并推入命令,最常见用于消息传递和任务队列