Redis 数据类型之列表(List)类型

 2016年08月18日    113     声明


  1. 列表(List)类型
  2. 列表类型中的命令及使用

1. 列表(List)类型

Redis 的列表(List)类型是按照插入顺序排序的字符串链表。该类型和数据结构中的普通链表一样,我们可以在其头部(LPUSH)和尾部(RPUSH)添加新的元素。在插入元素时,如果该键不存在,那么将创创建新列表。如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

一个List中最多可存储232-1(40亿)个元素。操作列表元素时,如果是从链表的两头插入或删除元素,操作效率会非常高。即使列表中已存储了百万条数据,该操作也可以在常量短的时间内完成。但是,将元素插入列表中间或是删除位于链表中间元素,那操作效率会非常的低。


2. 列表类型中的命令及使用

List类型中,有些命令是从列表两端进行操作的,如:LPUSHRPUSH等,这此命令操作效率很高。而有些需要指定操作位置上,如:LINDEXLRANGE等,这些命令操作效率较低。

2.1 创建列表/插入元素

一个列表可以通过LPUSHRPUSH方法创建并插入向列表的两端元素。向列表两端插入元素还可以使用LPUSHXRPUSHX方法,这两方法只能向已存在的列表中插入数据。如果需要向列表的指定位置插入元素,可以使用LINSERT方法,但该方法操作效率较低。

2.1.1 LPUSH - 向列表头插入元素

LPUSH key value [value ...]

将一个或多个值value插入到列表key的头部。

如果有多个value,那么从左到右依次插入列表。如果列表key不存在,首先会创建一个空列表再执行LPUSH操作。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:命令执行成功后,列表的长度;如果key存在,但不是List类型,会返回一个错误。

使用示例

# 加入单个元素
redis> LPUSH languages python
(integer) 1

# 加入重复元素
redis> LPUSH languages python
(integer) 2
redis> LRANGE languages 0 -1     # 列表允许重复元素
1) "python"
2) "python"

# 加入多个元素
redis> LPUSH mylist a b c
(integer) 3
redis> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"


2.1.2 LPUSHX - 当列表存在则将元素插入表头

LPUSHX key value

如果列表key存在且是List类型,则将值value插入到列表key的头部。

如果列表key不存在,则无操作。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:命令执行成功后,列表的长度;如果key存在,但不是List类型,会返回一个错误。

使用示例

# 对空列表执行 LPUSHX
redis> LLEN greet
(integer) 0
redis> LPUSHX greet "hello"    # LPUSHX 失败,因为列表为空
(integer) 0

# 对非空列表执行 LPUSHX
redis> LPUSH greet "hello" # 这次 LPUSHX 执行成功
(integer) 1
redis> LPUSHX greet "good morning" 
(integer) 2
redis> LRANGE greet 0 -1
1) "good morning"
2) "hello"

# 非列表类型,返回错误
redis> set key value
OK
redis> lpush key xxx
(error) WRONGTYPE Operation against a key holding the wrong kind of value


2.1.3 RPUSH - 将指定元素插入列表末尾

RPUSH key value [value ...]

将指定的一个或多个值,依次插入列表key的末尾。如果列表key不存在,会首先创建一个空列表,再执行RPUSH

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:执行RPUSH后列表的长度。

使用示例

# 添加单个元素
redis> RPUSH languages c
(integer) 1

# 添加重复元素
redis> RPUSH languages c
(integer) 2
redis> LRANGE languages 0 -1 # 列表允许重复元素
1) "c"
2) "c"

# 添加多个元素
redis> RPUSH mylist a b c
(integer) 3
redis> LRANGE mylist 0 -1
1) "a"
2) "b"
3) "c"


2.1.4 RPUSHX - 当列表存在则将元素插入表尾

RPUSHX key value

如果列表key存在且是List类型,则将值value插入到列表key的尾部。

如果列表key不存在,则无操作。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:执行RPUSHX后列表的长度。

使用示例

# key不存在
redis> LLEN greet
(integer) 0
# 对不存在的 key 进行 RPUSHX,PUSH 失败。
redis> RPUSHX greet "hello"
(integer) 0

# key 存在且是一个非空列表
# 先用 RPUSH 插入一个元素
redis> RPUSH greet "hi"         
(integer) 1
 # greet 是一个列表类型,RPUSHX 操作成功
redis> RPUSHX greet "hello"
(integer) 2
redis> LRANGE greet 0 -1
1) "hi"
2) "hello"


2.1.5 LINSERT - 将元素插入指定位置

LINSERT key BEFORE|AFTER pivot value

将元素value插入列表keypivot元素的之前或之后。

如果pivotkey不存在则不执行任何操作。如果key不是一个列表类型,则返回一个错误。

复杂度、返回值:

  • 时间复杂度:O(N)N查找pivot所经过元素的数量
  • 返回值:操作成功,则返回插入操作完成之后,列表的长度;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"

# 对一个非空列表插入,查找一个不存在的 pivot
redis> LINSERT mylist BEFORE "go" "let's"
(integer) -1                  # 失败

# 对一个空列表执行 LINSERT 命令
redis> EXISTS fake_list
(integer) 0
redis> LINSERT fake_list BEFORE "nono" "gogogog"
(integer) 0                                      # 失败


2.2 列表取值

获取列表中的元素可以使用LPOPRPOP方法,这两个方法会删除并返回头/尾元素。也可以使用LINDEX获取指定位置元素,或使用LRANGE获取指定区间的元素。

2.2.1 LPOP - 返回列表头元素

LPOP key

返回列表key的头元素。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:列表key中的头元素;如果key不存在,则返回nil

使用示例

redis> LLEN course
(integer) 0
redis> RPUSH course algorithm001
(integer) 1
redis> RPUSH course c++101
(integer) 2
redis> LPOP course  # 移除头元素
"algorithm001"


2.2.2 BLPOP - 阻塞并弹出头元素

BLPOP key [key ...] timeout

BLPOPLPOP命令的阻塞(blocking)版本,当指定列表中没有任何元素可供弹出的元素时,连接将被BLPOP命令阻塞,直到等待超时或有可弹出元素为止。

当指定多个key参数时,会按key的先后顺序依次检查各个列表,并弹出第一个非空列表的头元素。

timeout参数表示阻塞的时长,如果为0表示可以无限期延长阻塞。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:如果列表为空,返回一个nil。 否则,返回一个含有两个元素的列表,其中:第一个元素是被弹出元素所属的key,第二个元素是被弹出元素的

使用示例

该命令在以下情况下是非阻塞的:

BLPOP命令被调用时,如果指定key内至少有一个非空列表,那么该列表的头元素会弹出,并和key名一起返回给调用者。

如,现在有jobcommandrequest三个列表,其中 job 不存在,而commandrequest都是非空列表,则:

 # 确保key都被删除
redis> DEL job command request
(integer) 0

# 为command列表增加一个值
redis> LPUSH command "update system..."
(integer) 1
# 为request列表增加一个值
redis> LPUSH request "visit page"
(integer) 1

# job 列表为空,被跳过,紧接着 command 列表的第一个元素被弹出。
redis> BLPOP job command request 0       
1) "command"                     # 弹出元素所属列表的key
2) "update system..."            # 弹出元素的值

BLPOP在以下情况下是阻塞的:

如果所有指定的key都不存在或者是空列表,那么BLPOP命令将阻塞连接,直到等待超时,或有另外一个客户端向指定列表中的任意一个执行LPUSHRPUSH命令为止。


2.2.3 RPOP - 返回列表尾元素

LTRIM key start stop

移除并返回列表key的尾元素。

LTRIM使用闭区间,即:startstop索引位的元素都包含在取值范围内(如:LTRIM list 0 10结果是一个包含11元素的列表)。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:列表尾元素。当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"


2.2.4 BRPOP - 阻塞并弹出末尾元素

BRPOP key [key ...] timeout

BRPOPRPOP命令的阻塞(blocking)版本,当指定列表中没有任何元素可供弹出的元素时,连接将被BRPOP命令阻塞,直到等待超时或有可弹出元素为止。

当指定多个key参数时,会按key的先后顺序依次检查各个列表,并弹出第一个非空列表的末尾元素。

timeout参数表示阻塞的时长,如果为0表示可以无限期延长阻塞。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:如果列表为空,返回一个nil。 否则,返回一个含有两个元素的列表,其中:第一个元素是被弹出元素所属的key,第二个元素是被弹出元素的

使用示例

redis> LLEN course
(integer) 0

redis> RPUSH course algorithm001
(integer) 1

redis> RPUSH course c++101
(integer) 2

redis> BRPOP course 30
1) "course"             # 弹出元素的 key
2) "c++101"             # 弹出元素的值


2.2.5 LINDEX - 返回指定位置的元素

LINDEX key index

返回列表key中下标为index的元素。

index0开始计数,0表示第一个元素,1表示第二个元素,以次类推。也可以使用负数,-1表时倒数第一个元素,-2表时倒数第二个元素,以次类推

复杂度、返回值:

  • 时间复杂度:O(N)N为到达下标index所经过元素的数量
  • 返回值:列表中下标为index的元素;如果key不是列表类型,返回一个错误。如果index不在列表有效范围内,返回一个nil

使用示例

redis> LPUSH mylist "World"
(integer) 1
redis> LPUSH mylist "Hello"
(integer) 2
redis> LINDEX mylist 0
"Hello"
redis> LINDEX mylist -1
"World"
# index不在 mylist 的区间范围内
redis> LINDEX mylist 3
(nil)


2.2.6 LRANGE - 获取指定区间的元素

LRANGE key start stop

返回列表key中,以偏移量startstop指定的区间内的元素。

LRANGE使用闭区间,即:startstop索引位的元素都包含在取值范围内(如:LRANGE list 0 10会返回11元素)。当startstop超出有效索引位范围时不会引起错误,如果start大于最大下标值会返回一个空列表;如果stop大于最大下标值,会自动设置为最后一位。

复杂度、返回值:

  • 时间复杂度:O(S+N)S为偏移量startN为指定区间内元素的数量
  • 返回值:一个包含指定元素的列表

使用示例

redis> RPUSH fp-language lisp
(integer) 1
redis> LRANGE fp-language 0 0
1) "lisp"
redis> RPUSH fp-language scheme
(integer) 2
redis> LRANGE fp-language 0 1
1) "lisp"
2) "scheme"


2.3 列表修改/元素移动

Redis 提供了LSET命令,用于修改列表指定位置元素的值。除了这个命令外,我们还可以使用RPOPLPUSH将元素从一个列表移动到另一个列表。

2.3.1 LSET - 设置指定位元素

LSET key index value

设置列表key中下标为index的元素值为value。当index超出范围,或对一个空列表进行设置时,会返回一个错误。

复杂度、返回值:

  • 时间复杂度:为头/尾元素进行操作时为O(1);其它情况为O(N)N列表长度
  • 返回值:操作成功返回OK,否则返回错误信息。

使用示例

# 对空列表进行 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"
OK
redis> LRANGE job  0 0
1) "play game"

# index 超出范围
redis> LLEN list
(integer) 1
redis> LSET list 3 'out of range'
(error) ERR index out of range


2.3.2 RPOPLPUSH - 弹出尾元素,将弹出元素插入另一列表的开头

RPOPLPUSH source destination

RPOPLPUSH命令包含以下两个原子操作:

  • 将列表source的尾元素弹出,并返回给客户端。
  • source弹出的元素,作为destination列表的头元素插入。

如果source不存在,返回nil。如果sourcedestination相同,就会把尾元素移动至开头,这叫做列表的旋转(rotation)操作。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:被弹出的元素。

使用示例

# source 和 destination 不同
redis> LRANGE alpha 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
redis> RPOPLPUSH alpha reciver
"d"
redis> LRANGE alpha 0 -1
1) "a"
2) "b"
3) "c"
redis> LRANGE reciver 0 -1
1) "d"
# 再执行一次,表明 RPOP 和 LPUSH 的位置正确
redis> RPOPLPUSH alpha reciver
"c"
redis> LRANGE alpha 0 -1
1) "a"
2) "b"
redis> LRANGE reciver 0 -1
1) "c"
2) "d"


# source 和 destination 相同
redis> LRANGE number 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
redis> RPOPLPUSH number number
"4"
# 4 被旋转到了表头
redis> LRANGE number 0 -1
1) "4"
2) "1"
3) "2"
4) "3"
redis> RPOPLPUSH number number
"3"
redis> LRANGE number 0 -1
1) "3"
2) "4"
3) "1"
4) "2"


2.3.3 BRPOPLPUSH - 阻塞并弹出尾元素,将弹出元素插入另一列表的开头

BRPOPLPUSH source destination timeout

BRPOPLPUSHRPOPLPUSH命令的阻塞版本。当指定的源列表source不为空时,其表现和RPOPLPUSH一样。当source为空时,连接将被BRPOP命令阻塞,直到等待超时或有可弹出元素为止。

timeout参数表示阻塞的时长,如果为0表示可以无限期延长阻塞。

复杂度、返回值:

  • 时间复杂度:O(1)
  • 返回值:如果指定时间内没有任何元素弹出,返回一个nil。 否则,返回一个含有两个元素的列表,其中:第一个元素是被弹出元素所属的key,第二个元素是被弹出元素的

使用示例

# 非空列表
redis> BRPOPLPUSH msg reciver 500
"hello moto"                        # 弹出元素的值
(3.38s)                             # 等待时长
redis> LLEN reciver
(integer) 1
redis> LRANGE reciver 0 0
1) "hello moto"


# 空列表
redis> BRPOPLPUSH msg reciver 1
(nil)
(1.34s)


2.4 元素删除/列表裁剪

通过LREM命令可以移除列表中不再需要的元素。而LTRIM可以从指定区间范围裁剪列表。

2.4.1 LREM - 移除元素

LREM key count value

移除指定数量为countvalue值。count可能有以下几种情况:

  • count >0 : 从表头开始向表尾搜索,移除值为value,数量为 count的元素。
  • count < 0 : 从表尾开始向表头搜索,移除值为value,数量为 count绝对值的元素。
  • count = 0 : 移除表中所值为value的元素。

复杂度、返回值:

  • 时间复杂度:O(N)N列表长度
  • 返回值:被移除元素的数量。key不存在时,返回0

使用示例

# 创建一个列表,内容排列是
# morning hello morning helllo morning

redis> LPUSH greet "morning"
(integer) 1
redis> LPUSH greet "hello"
(integer) 2
redis> LPUSH greet "morning"
(integer) 3
redis> LPUSH greet "hello"
(integer) 4
redis> LPUSH greet "morning"
(integer) 5
# 查看所有元素
redis> LRANGE greet 0 4
1) "morning"
2) "hello"
3) "morning"
4) "hello"
5) "morning"

# 移除从表头到表尾,最先发现的两个 morning
redis> LREM greet 2 morning     
(integer) 2

redis> LLEN greet               # 还剩 3 个元素
(integer) 3

redis> LRANGE greet 0 2
1) "hello"
2) "hello"
3) "morning"

# 移除从表尾到表头,第一个 morning
redis> LREM greet -1 morning    
(integer) 1
# 剩下两个元素
redis> LLEN greet
(integer) 2

redis> LRANGE greet 0 1
1) "hello"
2) "hello"
# 移除表中所有 hello
redis> LREM greet 0 hello      
(integer) 2

redis> LLEN greet
(integer) 0


2.4.2 LTRIM - 列表裁剪

LTRIM key start stop

保留列表key中,偏移量startstop指定的区间内的元素,裁剪其余元素。

LTRIM使用闭区间,即:startstop索引位的元素都包含在取值范围内(如:LTRIM list 0 10结果是一个包含11元素的列表)。

复杂度、返回值:

  • 时间复杂度:O(N)N移除元素的数量
  • 返回值:操作成功返回OK,否则返回错误信息。

使用示例

# 1. start 和 stop 都在列表的索引范围之内

# alpha 是一个包含 5 个字符串的列表
redis> LRANGE alpha 0 -1
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"

# 删除 alpha 列表索引为 0 的元素
redis> LTRIM alpha 1 -1
OK
# "h" 已被删除
redis> LRANGE alpha 0 -1       
1) "e"
2) "l"
3) "l"
4) "o"

# 2. stop 大于最大下标值
# 保留 alpha 列表索引 1 至索引 10086 上的元素
redis> LTRIM alpha 1 10086
OK
# 只有索引 0 上的元素 "e" 被删除了,其他元素还在
redis> LRANGE alpha 0 -1       
1) "l"
2) "l"
3) "o"

# 3. start 和 stop 都大于列表的最大下标,并且 start < stop
redis> LTRIM alpha 10086 123321
OK

redis> LRANGE alpha 0 -1        # 列表被清空
(empty list or set)


# 4. start 和 stop 都大于列表的最大下标,并且 start > stop
# 重新建立一个新列表
redis> RPUSH new-alpha "h" "e" "l" "l" "o"
(integer) 5

redis> LRANGE new-alpha 0 -1
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"
# 执行 LTRIM
redis> LTRIM new-alpha 123321 10086    
OK
# 同样被清空
redis> LRANGE new-alpha 0 -1
(empty list or set)