目录
- List类型介绍
- 特点
- List数据结构
- 附:3.2以前的版本(介绍一下压缩列表和双向链表)
- 压缩列表ZipList
- 双向链表LinkedList
- 常用命令
- lpush
- 示例
- lpushx
- 示例
- rpush
- 示例
- rpushx
- 示例
- LPOP
- 示例
- RPOP
- 示例
- BLPOP
- 非阻塞行为
- 阻塞行为
- 相同的 key 被多个客户端同时阻塞
- 在 MULTI/EXEC 事务中的 BLPOP
- BRPOP
- 示例
- LLEN
- 示例
- LRANGE
- 注意 LRANGE 命令和编程语言区间函数的区别
- 超出范围的下标
- 示例
- LREM
- 示例
- LSET
- 示例
- LTRIM
- 注意 LTRIM 命令和编程语言区间函数的区别
- 超出范围的下标
- 示例
- LINDEX
- 示例
- LINSERT
- 示例
- RPOPLPUSH
- 示例
- 应用1:安全的队列
- 应用2:循环列表
- BRPOPLPUSH
- 示例
- 应用1:安全队列
- 应用2:循环列表
List类型介绍
- 单键多值:Redis 列表是简单的字符串列表,按照插⼊顺序排序。
- 你可以添加⼀个元素到列表的头部(左边)或者尾部(右边)。
- 它的底层实际是个双向链表,对两端的操作性能很⾼,通过索引下标的操作中间的节点性能会较差。
- Redis 中列表(List)类型是用来存储多个有序的字符串,列表中的每个字符串成为元素Eelement),一个列表最多可以存储 2^32-1 个元素。
- 在 Redis 中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,可以充当栈和队列的角色,在实际开发中有很多应用场景。
特点
- 列表中的元素是有序的,即可以通过索引下标获取某个元素或者某个范围内的元素列表;
- 列表中的元素可以是重复的
List数据结构
-
Redis3.2 版本开始,List 类型数据使用的底层数据结构是快速链表,快速列表是以压缩列表为节点的双向链表,将双向链表按段切分,每一段使用压缩列表进行内存的连续存储,多个压缩列表通过 prev 和 next 指针组成的双向链。
-
⾸先在列表元素较少的情况下会使⽤⼀块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着⼀起存储,分配的是⼀块连续的内存。
-
当数据量⽐较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太⼤,会⽐较浪费空间。⽐如这个列表⾥存的只是 int 类型的数据,结构上还需要两个额外的指针 prev 和 next。
-
Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个 ziplist 使⽤双向指针串起来使⽤。这样既满⾜了快速的插⼊删除性能,⼜不会出现太⼤的空间冗余。
考虑到链表的以上缺点,Redis 后续版本对列表数据结构进行改造,使用 QucikList 代替了 ZipList 和 LinkedList。 作为 ZipList 和 LinkedList 的混合体,它将 LinkedList 按段切分,每一段使用 ZipList 来紧凑存储,多个 ZipList 之间使用双向指针串接起来。
附:3.2以前的版本(介绍一下压缩列表和双向链表)
压缩列表ZipList
- 压缩列表是一块连续的内存空间 (像内存连续的数组,但每个元素长度不同),一个 ziplist 可以包含多个节点(entry)。元素之间紧挨着存储,没有任何冗余空隙。
- 压缩列表的本质就是一个数组,只不过是增加了 “列表长度”、“尾部偏移量”、“列表元素个数” 以及 “列表结束标识”,这样的话就有利于快速的寻找列表的首、尾节点.压缩列表将表中每一项存放在前后连续的地址空间内,每一项因占用的空间不同,而采用变长编码。由于内存是连续分配的,所以遍历速度很快。
- 当我们的 List 列表数据量比较少的时候,且存储的数据轻量的(如小整数值、短字符串)时候, Redis 就会通过压缩列表来进行底层实现。
双向链表LinkedList
-
LinkedList 是标准的双向链表,Node 节点包含 prev 和 next 指针,分别指向后继与前驱节点,因此从双向链表中的任意一个节点开始都可以很方便地访问其前驱与后继节点。
-
LinkedList 可以进行双向遍历;添加删除元素快 O(1),查找元素慢 O(n),高效实现了 LPUSH 、RPOP、RPOPLPUSH,但由于需要为每个节点分配额外的内存空间,所以会浪费一定的内存空间。这种编码方式适用于元素数量较多或者元素较大的场景。
-
LinkedList 结构为链表提供了表头指针 head、表尾指针 tail,以及节点数量计算 len。下图展示一个由 list 结构和三个 listNode 节点组成的链表:
-
Redis 的链表实现的特性可以总结如下:
- 双端:链表节点带有 prev 和 next 指针,获取某个节点的前一节点和后一节点的复杂度都是 O(1);
- 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以 NULL 为终点;
- 表头指针/表尾指针:通过 list 结构的 head 指针和 tail 指针,获取链表的表头节点和表尾节点的复杂度为 O(1);
- 链表长度计数器:通过 list 结构的 len 属性来对 list 的链表节点进行计数,获取节点数量的复杂度为O(1);
- 多态:链表节点使用 void* 指针来保存节点值,并通过 list 结构的 dup、free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
- 使用链表的附加空间相对太高,因为 64bit 系统中指针是 8 个字节,所以 prev 和 next 指针需要占据 16 个字节,且链表节点在内存中单独分配,会加剧内存的碎片化,影响内存管理效率
常用命令
- lpush/rpush <key><value1><value2><value3> … 从左边/右边插⼊⼀个或多个值。
- lpop/rpop <key> 从左边/右边吐出⼀个值。值在键在,值光键亡。
- rpoplpush <key1><key2> 从 <key1> 列表右边吐出⼀个值,插到 <key2> 列表左边。
- lrange <key><start><stop> 按照索引下标获得元素(从左到右)
- lrange mylist 0 -1 0左边第⼀个,-1右边第⼀个,(0-1表示获取所有)
- lindex <key><index> 按照索引下标获得元素(从左到右)
- llen <key> 获得列表⻓度
- linsert <key> before <value><newvalue> 在 <value> 的后⾯插⼊值 <newvalue>
- lrem <key><n><value> 从左边删除 n 个 value (从左到右)
- lset <key><index><value> 将列表 key 下标为 index 的值替换成 value
lpush
- 语法:lpush key value [value …]
- 解释:
- 将一个或多个值 value 插入到列表 key 的表头
- 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头:比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
- 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
- 当 key 存在但不是列表类型时,返回一个错误。
- 注: 在 Redis 2.4 版本以前的 LPUSH 命令,都只接受单个 value 值。
- 时间复杂度:O(1)
- 返回值:执行 LPUSH 命令后,列表的长度。
示例
# 加入单个元素
127.0.0.1:6379[3]> LPUSH languages python
(integer) 1
# 加入重复元素
127.0.0.1:6379[3]> LPUSH languages python
(integer) 2
127.0.0.1:6379[3]> LRANGE languages 0 -1 # 列表允许重复元素
1) "python"
2) "python"
# 加入多个元素
127.0.0.1:6379[3]> LPUSH mylist a b c
(integer) 3
127.0.0.1:6379[3]> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"
lpushx
- 语法:lpushx key value
- 解释:
- 将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。
- 和 LPUSH 命令相反,当 key 不存在时, LPUSHX 命令什么也不做
- 时间复杂度:O(1)
- 返回值:LPUSHX 命令执行之后,表的长度
示例
# 对空列表执行 LPUSHX
127.0.0.1:6379[3]> LLEN greet # greet 是一个空列表
(integer) 0
127.0.0.1:6379[3]> LPUSHX greet "hello" # 尝试 LPUSHX,失败,因为列表为空
(integer) 0
# 对非空列表执行 LPUSHX
127.0.0.1:6379[3]> LPUSH greet "hello" # 先用 LPUSH 创建一个有一个元素的列表
(integer) 1
127.0.0.1:6379[3]> LPUSHX greet "good morning" # 这次 LPUSHX 执行成功
(integer) 2
127.0.0.1:6379[3]> LRANGE greet 0 -1
1) "good morning"
2) "hello"
rpush
- 语法:rpush key value [value …]
- 解释:
- 将一个或多个值 value 插入到列表 key 的表尾(最右边)。
- 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行
RPUSH mylist a b c
,得出的结果列表为 a b c ,等同于执行命令RPUSH mylist a
、RPUSH mylist b
、RPUSH mylist c
。 - 如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
- 当 key 存在但不是列表类型时,返回一个错误。
- 注:在 Redis 2.4 版本以前的 RPUSH 命令,都只接受单个 value 值。
- 时间复杂度:O(1)
- 返回值:执行 RPUSH 操作后,表的长度。
示例
# 添加单个元素
127.0.0.1:6379[3]> RPUSH languages c
(integer) 1
# 添加重复元素
127.0.0.1:6379[3]> RPUSH languages c
(integer) 2
127.0.0.1:6379[3]> LRANGE languages 0 -1 # 列表允许重复元素
1) "c"
2) "c"
# 添加多个元素
127.0.0.1:6379[3]> RPUSH mylist a b c
(integer) 3
127.0.0.1:6379[3]> LRANGE mylist 0 -1
1) "a"
2) "b"
3) "c"
rpushx
- 语法:rpushx key value
- 解释:
- 将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。
- 和 RPUSH 命令相反,当 key 不存在时, RPUSHX 命令什么也不做。
- 时间复杂度:O(1)
- 返回值:RPUSHX 命令执行之后,表的长度
示例
# key 不存在
127.0.0.1:6379[3]> LLEN greet
(integer) 0
127.0.0.1:6379[3]> RPUSHX greet "hello" # 对不存在的 key 进行 RPUSHX,PUSH 失败。
(integer) 0
# key 存在且是一个非空列表
127.0.0.1:6379[3]> RPUSH greet "hi" # 先用 RPUSH 插入一个元素
(integer) 1
127.0.0.1:6379[3]> RPUSHX greet "hello" # greet 现在是一个列表类型,RPUSHX 操作
成功。
(integer) 2
127.0.0.1:6379[3]> LRANGE greet 0 -1
1) "hi"
2) "hello"
LPOP
- 语法:lpop key
- 解释:移除并返回列表 key 的头元素。
- 时间复杂度: O(1)
- 返回值:
- 列表的头元素。
- 当 key 不存在时,返回 nil 。
示例
127.0.0.1:6379[3]> LLEN course
(integer) 0
127.0.0.1:6379[3]> RPUSH course algorithm001
(integer) 1
127.0.0.1:6379[3]> RPUSH course c++101
(integer) 2
127.0.0.1:6379[3]> LPOP course # 移除头元素
"algorithm001"
RPOP
- 语法:rpop key
- 解释:移除并返回列表 key 的尾元素。
- 时间复杂度: O(1)
- 返回值:
- 列表的尾元素。
- 当 key 不存在时,返回 nil 。
示例
127.0.0.1:6379[3]> RPUSH mylist "one"
(integer) 1
127.0.0.1:6379[3]> RPUSH mylist "two"
(integer) 2
127.0.0.1:6379[3]> RPUSH mylist "three"
(integer) 3
127.0.0.1:6379[3]> RPOP mylist # 返回被弹出的元素
"three"
127.0.0.1:6379[3]> LRANGE mylist 0 -1 # 列表剩下的元素
1) "one"
2) "two"
BLPOP
- 语法:blpop key [key …] timeout
- 解释:
- BLPOP 是列表的阻塞式(blocking)弹出原语。
- 它是 LPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
- 当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。
非阻塞行为
-
当 BLPOP 被调用时,如果给定 key 内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。
-
当存在多个给定 key 时, BLPOP 按给定 key 参数排列的先后顺序,依次检查各个列表。
-
假设现在有 job 、 command 和 request 三个列表,其中 job 不存在, command 和request 都持有非空列表。考虑以下命令:
BLPOP job command request 0
-
BLPOP 保证返回的元素来自 command ,因为它是按”查找 job -> 查找 command -> 查找 request “这样的顺序,第一个找到的非空列表。
127.0.0.1:6379[3]> DEL job command request # 确保 key 都被删除 (integer) 0 127.0.0.1:6379[3]> LPUSH command "update system..." # 为 command 列表增加一个值 (integer) 1 127.0.0.1:6379[3]> LPUSH request "visit page" # 为 request 列表增加一个值 (integer) 1 127.0.0.1:6379[3]> BLPOP job command request 0 # job 列表为空,被跳过,紧接着command 列表的第一个元素被弹出。 1) "command" # 弹出元素所属的列表 2) "update system..." # 弹出元素所属的值
阻塞行为
-
如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接,直到等待超时,或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
-
超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
127.0.0.1:6379[3]> EXISTS job # 确保两个 key 都不存在 (integer) 0 127.0.0.1:6379[3]> EXISTS command (integer) 0 127.0.0.1:6379[3]> BLPOP job command 300 # 因为 key 一开始不存在,所以操作会被阻塞,直到另一客户端对 job 或者 command 列表进行 PUSH 操作。 1) "job" # 这里被 push 的是 job 2) "do my home work" # 被弹出的值 (26.26s) # 等待的秒数 127.0.0.1:6379[3]> BLPOP job command 5 # 等待超时的情况 (nil) (5.66s) # 等待的秒数
相同的 key 被多个客户端同时阻塞
- 相同的 key 可以被多个客户端同时阻塞。
- 不同的客户端被放进一个队列中,按『先阻塞先服务』(first-BLPOP,first-served)的顺序为 key 执行 BLPOP 命令。
在 MULTI/EXEC 事务中的 BLPOP
-
BLPOP 可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在MULTI / EXEC 块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行 LPUSH 或 RPUSH 命令。
-
因此,一个被包裹在 MULTI / EXEC 块内的 BLPOP 命令,行为表现得就像 LPOP 一样,对空列表返回 nil ,对非空列表弹出列表元素,不进行任何阻塞操作。
# 对非空列表进行操作 127.0.0.1:6379[3]> RPUSH job programming (integer) 1 127.0.0.1:6379[3]> MULTI OK 127.0.0.1:6379[3]> BLPOP job 30 QUEUED 127.0.0.1:6379[3]> EXEC # 不阻塞,立即返回1) "job"2) "programming" # 对空列表进行操作 127.0.0.1:6379[3]> LLEN job # 空列表 (integer) 0 127.0.0.1:6379[3]> MULTI OK 127.0.0.1:6379[3]> BLPOP job 30 QUEUED 127.0.0.1:6379[3]> EXEC # 不阻塞,立即返回 1) (nil)
-
时间复杂度: O(1)
-
返回值:
- 如果列表为空,返回一个 nil 。
- 否则,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值
BRPOP
- 语法:brpop key [key …] timeout
- 解释:
- BRPOP 是列表的阻塞式(blocking)弹出原语。
- 它是 RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
- 当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。
- 关于阻塞操作的更多信息,请查看 BLPOP 命令, BRPOP 除了弹出元素的位置和 BLPOP 不同之外,其他表现一致。
- 时间复杂度:O(1)
- 返回值:
- 假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。
- 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
示例
127.0.0.1:6379[3]> LLEN course
(integer) 0
127.0.0.1:6379[3]> RPUSH course algorithm001
(integer) 1
127.0.0.1:6379[3]> RPUSH course c++101
(integer) 2
127.0.0.1:6379[3]> BRPOP course 30
1) "course" # 弹出元素的 key
2) "c++101" # 弹出元素的值
LLEN
- 语法:llen key
- 解释:
- 返回列表 key 的长度。
- 如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
- 如果 key 不是列表类型,返回一个错误。
- 时间复杂度:O(1)
- 返回值:列表 key 的长度。
示例
# 空列表
127.0.0.1:6379[3]> LLEN job
(integer) 0
# 非空列表
127.0.0.1:6379[3]> LPUSH job "cook food"
(integer) 1
127.0.0.1:6379[3]> LPUSH job "have lunch"
(integer) 2
127.0.0.1:6379[3]> LLEN job
(integer) 2
LRANGE
- 语法:lrange key start stop
- 解释:
- 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
- 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
- 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
注意 LRANGE 命令和编程语言区间函数的区别
- 假如你有一个包含一百个元素的列表,对该列表执行 LRANGE list 0 10 ,结果是一个包含 11 个元素的列表,这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如 Ruby 的 Range.new 、 Array#slice 和 Python的 range() 函数。
超出范围的下标
- 超出范围的下标值不会引起错误。
- 如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stop , LRANGE 返回一个空列表。
- 如果 stop 下标比 end 下标还要大,Redis 将 stop 的值设置为 end 。
- 时间复杂度:O(S+N), S 为偏移量 start , N 为指定区间内元素的数量。
- 返回值:一个列表,包含指定区间内的元素。
示例
# 空列表
127.0.0.1:6379[3]> RPUSH fp-language lisp
(integer) 1
127.0.0.1:6379[3]> LRANGE fp-language 0 0
1) "lisp"
127.0.0.1:6379[3]> RPUSH fp-language scheme
(integer) 2
127.0.0.1:6379[3]> LRANGE fp-language 0 1
1) "lisp"
2) "scheme"
LREM
- 语法:lrem key count value
- 解释:
- 根据参数 count 的值,移除列表中与参数 value 相等的元素。
- count 的值可以是以下几种:
- count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
- count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
- count = 0 : 移除表中所有与 value 相等的值。
- 时间复杂度:O(N), N 为列表的长度。
- 返回值:被移除元素的数量。因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。
示例
# 空列表
# 先创建一个表,内容排列是
# morning hello morning helllo morning
127.0.0.1:6379[3]> LPUSH greet "morning"
(integer) 1
127.0.0.1:6379[3]> LPUSH greet "hello"
(integer) 2
127.0.0.1:6379[3]> LPUSH greet "morning"
(integer) 3
127.0.0.1:6379[3]> LPUSH greet "hello"
(integer) 4
127.0.0.1:6379[3]> LPUSH greet "morning"
(integer) 5
127.0.0.1:6379[3]> LRANGE greet 0 4 # 查看所有元素
1) "morning"
2) "hello"
3) "morning"
4) "hello"
5) "morning"
127.0.0.1:6379[3]> LREM greet 2 morning # 移除从表头到表尾,最先发现的两个 morning
(integer) 2 # 两个元素被移除
127.0.0.1:6379[3]> LLEN greet # 还剩 3 个元素
(integer) 3
127.0.0.1:6379[3]> LRANGE greet 0 2
1) "hello"
2) "hello"
3) "morning"
127.0.0.1:6379[3]> LREM greet -1 morning # 移除从表尾到表头,第一个 morning
(integer) 1
127.0.0.1:6379[3]> LLEN greet # 剩下两个元素
(integer) 2
127.0.0.1:6379[3]> LRANGE greet 0 1
1) "hello"
2) "hello"
127.0.0.1:6379[3]> LREM greet 0 hello # 移除表中所有 hello
(integer) 2 # 两个 hello 被移除
127.0.0.1:6379[3]> LLEN greet
(integer) 0
LSET
- 语法:lset key index value
- 解释:
- 将列表 key 下标为 index 的元素的值设置为 value 。
- 当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
- 关于列表下标的更多信息,请参考 LINDEX 命令。
- 时间复杂度:对头元素或尾元素进行 LSET 操作,复杂度为 O(1)。其他情况下,为 O(N), N 为列表的长度。
- 返回值:操作成功返回 ok ,否则返回错误信息。
示例
# 对空列表(key 不存在)进行 LSET
127.0.0.1:6379[3]> EXISTS list
(integer) 0
127.0.0.1:6379[3]> LSET list 0 item
(error) ERR no such key
# 对非空列表进行 LSET
127.0.0.1:6379[3]> LPUSH job "cook food"
(integer) 1
127.0.0.1:6379[3]> LRANGE job 0 0
1) "cook food"
127.0.0.1:6379[3]> LSET job 0 "play game"
OK
127.0.0.1:6379[3]> LRANGE job 0 0
1) "play game"
# index 超出范围
127.0.0.1:6379[3]> LLEN list # 列表长度为 1
(integer) 1
127.0.0.1:6379[3]> LSET list 3 'out of range'
(error) ERR index out of range
LTRIM
- 语法:ltrim key start stop
- 解释:
- 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
- 举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
- 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
- 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
- 当 key 不是列表类型时,返回一个错误。
- LTRIM 命令通常和 LPUSH 命令或 RPUSH 命令配合使用,举个例子
LPUSH log newest_log LTRIM log 0 99
- 这个例子模拟了一个日志程序,每次将最新日志 newest_log 放到 log 列表中,并且只保留最新的 100 项。注意当这样使用 LTRIM 命令时,时间复杂度是 O(1),因为平均情况下,每次只有一个元素被移除。
注意 LTRIM 命令和编程语言区间函数的区别
- 假如你有一个包含一百个元素的列表 list ,对该列表执行 LTRIM list 0 10 ,结果是一个包含 11 个元素的列表,这表明 stop 下标也在 LTRIM 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如 Ruby 的 Range.new 、 Array#slice 和 Python的 range() 函数。
超出范围的下标
-
超出范围的下标值不会引起错误。
-
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stop , LTRIM 返回一个空列表(因为 LTRIM 已经将整个列表清空)。
-
如果 stop 下标比 end 下标还要大,Redis 将 stop 的值设置为 end 。
-
时间复杂度:O(N), N 为被移除的元素的数量。
-
返回值:命令执行成功时,返回 ok 。
示例
# 一般情况下标
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # 建立一个 5 元素的列表
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"
127.0.0.1:6379[3]> LTRIM alpha 1 -1 # 删除索引为 0 的元素
OK
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # "h" 被删除
1) "e"
2) "l"
3) "l"
4) "o"
# stop 下标比元素的最大下标要大
127.0.0.1:6379[3]> LTRIM alpha 1 10086
OK
127.0.0.1:6379[3]> LRANGE alpha 0 -1
1) "l"
2) "l"
3) "o"
# start 和 stop 下标都比最大下标要大,且 start < sotp
127.0.0.1:6379[3]> LTRIM alpha 10086 200000
OK
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # 整个列表被清空,等同于 DEL alpha
(empty list or set)
# start > stop
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # 在新建一个列表
1) "h"
2) "u"
3) "a"
4) "n"
5) "g"
6) "z"
127.0.0.1:6379[3]> LTRIM alpha 10086 4
OK
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # 列表同样被清空
(empty list or set)
LINDEX
- 语法:lindex key index
- 解释:
- 返回列表 key 中,下标为 index 的元素。
- 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
- 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
- 如果 key 不是列表类型,返回一个错误。
- 时间复杂度:
- O(N), N 为到达下标 index 过程中经过的元素数量。
- 因此,对列表的头元素和尾元素执行 LINDEX 命令,复杂度为 O(1)。
- 返回值:
- 列表中下标为 index 的元素。
- 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil 。
示例
127.0.0.1:6379[3]> LPUSH mylist "World"
(integer) 1
127.0.0.1:6379[3]> LPUSH mylist "Hello"
(integer) 2
127.0.0.1:6379[3]> LINDEX mylist 0
"Hello"
127.0.0.1:6379[3]> LINDEX mylist -1
"World"
127.0.0.1:6379[3]> LINDEX mylist 3 # index 不在 mylist 的区间范围内
(nil)
LINSERT
- 语法:linsert key BEFORE|AFTER pivot value
- 解释:
- 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
- 当 pivot 不存在于列表 key 时,不执行任何操作。
- 当 key 不存在时, key 被视为空列表,不执行任何操作。
- 如果 key 不是列表类型,返回一个错误。
- 时间复杂度:O(N), N 为寻找 pivot 过程中经过的元素数量。
- 返回值:
- 如果命令执行成功,返回插入操作完成之后,列表的长度。
- 如果没有找到 pivot ,返回 -1 。
- 如果 key 不存在或为空列表,返回 0
示例
127.0.0.1:6379[3]> RPUSH mylist "Hello"
(integer) 1
127.0.0.1:6379[3]> RPUSH mylist "World"
(integer) 2
127.0.0.1:6379[3]> LINSERT mylist BEFORE "World" "There"
(integer) 3
127.0.0.1:6379[3]> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
# 对一个非空列表插入,查找一个不存在的 pivot
127.0.0.1:6379[3]> LINSERT mylist BEFORE "go" "let's"
(integer) -1 # 失败
# 对一个空列表执行 LINSERT 命令
127.0.0.1:6379[3]> EXISTS fake_list
(integer) 0
127.0.0.1:6379[3]> LINSERT fake_list BEFORE "nono" "gogogog"
(integer) 0 # 失败
RPOPLPUSH
- 语法:rpoplpush source destination
- 解释:
- 命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
- 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
- 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
- 举个例子,你有两个列表 source 和 destination , source 列表有元素 a, b, c ,destination 列表有元素 x, y, z ,执行 RPOPLPUSH source destination 之后, source 列表包含元素 a, b , destination 列表包含元素 c, x, y, z ,并且元素 c 会被返回给客户端。
- 如果 source 不存在,值 nil 被返回,并且不执行其他动作。
- 如果 source 和 destination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
- 命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
- 时间复杂度:O(1)
- 返回值:被弹出的元素。
示例
# source 和 destination 不同
127.0.0.1:6379[3]> LRANGE alpha 0 -1 # 查看所有元素
1) "a"
2) "b"
3) "c"
4) "d"
127.0.0.1:6379[3]> RPOPLPUSH alpha reciver # 执行一次 RPOPLPUSH 看看
"d"
127.0.0.1:6379[3]> LRANGE alpha 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379[3]> LRANGE reciver 0 -1
1) "d"
127.0.0.1:6379[3]> RPOPLPUSH alpha reciver # 再执行一次,证实 RPOP 和 LPUSH 的位置正确
"c"
127.0.0.1:6379[3]> LRANGE alpha 0 -1
1) "a"
2) "b"
127.0.0.1:6379[3]> LRANGE reciver 0 -1
1) "c"
2) "d"
# source 和 destination 相同
127.0.0.1:6379[3]> LRANGE number 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379[3]> RPOPLPUSH number number
"4"
127.0.0.1:6379[3]> LRANGE number 0 -1 # 4 被旋转到了表头
1) "4"
2) "1"
3) "2"
4) "3"
127.0.0.1:6379[3]> RPOPLPUSH number number
"3"
127.0.0.1:6379[3]> LRANGE number 0 -1 # 这次是 3 被旋转到了表头
1) "3"
2) "4"
3) "1"
4) "2"
应用1:安全的队列
- Redis 的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个客户端通过 LPUSH 命令将消息放入队列中,而另一个客户端通过 RPOP 或者 BRPOP 命令取出队列中等待时间最长的消息。
- 不幸的是,上面的队列方法是『不安全』的,因为在这个过程中,一个客户端可能在取出一个消息之后崩溃,而未处理完的消息也就因此丢失。
- 使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH )可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,如果一切正常的话,当一个客户端完成某个消息的处理之后,可以用 LREM 命令将这个消息从备份表删除。
- 最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过一定处理时限的消息重新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。
应用2:循环列表
- 通过使用相同的 key 作为 RPOPLPUSH 命令的两个参数,客户端可以用一个接一个地获取列表元素的方式,取得列表的所有元素,而不必像 LRANGE 命令那样一下子将所有列表元素都从服务器传送到客户端中(两种方式的总复杂度都是 O(N))。
- 以上的模式甚至在以下的两个情况下也能正常工作:
- 有多个客户端同时对同一个列表进行旋转(rotating),它们获取不同的元素,直到所有元素都被读取完,之后又从头开始。
- 有客户端在向列表尾部(右边)添加新元素。
- 这个模式使得我们可以很容易实现这样一类系统:有 N 个客户端,需要连续不断地对一些元素进行处理,而且处理的过程必须尽可能地快。一个典型的例子就是服务器的监控程序:
- 它们需要在尽可能短的时间内,并行地检查一组网站,确保它们的可访问性。
- 注意,使用这个模式的客户端是易于扩展(scala)且安全(reliable)的,因为就算接收到元素的客户端失败,元素还是保存在列表里面,不会丢失,等到下个迭代来临的时候,别的客户端又可以继续处理这些元素了。
BRPOPLPUSH
- 语法:brpoplpush source destination timeout
- 解释:
- BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本,当给定列表 source 不为空时, BRPOPLPUSH的表现和 RPOPLPUSH 一样。
- 当列表 source 为空时, BRPOPLPUSH 命令将阻塞连接,直到等待超时,或有另一个客户端对 source 执行 LPUSH 或 RPUSH 命令为止。
- 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
- 更多相关信息,请参考 RPOPLPUSH 命令。
- 时间复杂度:O(1)
- 返回值:
- 假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。
- 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。
示例
# 非空列表
127.0.0.1:6379[3]> BRPOPLPUSH msg reciver 500
"hello moto" # 弹出元素的值
(3.38s) # 等待时长
127.0.0.1:6379[3]> LLEN reciver
(integer) 1
127.0.0.1:6379[3]> LRANGE reciver 0 0
1) "hello moto"
# 空列表
127.0.0.1:6379[3]> BRPOPLPUSH msg reciver 1
(nil)
(1.34s)
应用1:安全队列
参考 RPOPLPUSH 命令的『安全队列』模式。
应用2:循环列表
参考 RPOPLPUSH 命令的『循环列表』模式