list 数据类型简述
Redis 当中的 list 就是指简单的字符串列表。当您使用 list 数据类型时,需要首先指定一个 key ,然后使用相关的命令从列表的 L(left,左) 和 R (right,右)添加元素,这些元素按照添加的先后顺序进行排列。一个列表可以拥有 2^32-1 个元素,当列表中的最后一个元素弹出时,该结构自动删除。
关于 list 底层数据结构的实现(有些资料也叫编码方式),随着版本的更替有所不同:
- 早期版本使用 linkedlist(双端列表)和 ziplist(压缩列表)
- 从 Redis 3.2 开启,使用 linkedlist + ziplist 组成的 quicklist。
- 从 redis 7.0 开始,还是使用 quicklist,只不过将 ziplist 替换为 listpack
当列表中的元素较少时,Redis 会使用一块连续的内存来存储这些元素,这个连续的数据结构被称为 ziplist,此时的所有元素都是紧紧挨着一起存储。ziplist 是 Redis 为了节省内存而开发的,一个 ziplist 可以包含任意多的节点,每个节点都可以保存一个字符数组或整数值。当数据量较大时,Redis 会使用 quicklist 结构存储元素,之所以这么做,是因为当数据量大后,如果继续采用普通链表存储列表元素,带来的后果是:
- 压缩率不明显且修改数据的效率不高
- 占用且浪费了大量的内存空间
- 查询效率不高
可以从源代码中看到一些情况:
Shell > vim /usr/local/src/redis-7.2.1/src/quicklist.h
...
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *entry;
size_t sz; /* entry size in bytes */
unsigned int count : 16; /* count of items in listpack */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* PLAIN==1 or PACKED==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
unsigned int extra : 9; /* more bits to steal for future usage */
} quicklistNode;
...
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all listpacks */
unsigned long len; /* number of quicklistNodes */
signed int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
lpush
命令和 rpush
命令
这里的 l
和 r
代表着往 head 的 L(left,左) 和 R(right,右)位置插入数据。
# 插入一些 list 数据
192.168.100.3:6379> select 2
OK
192.168.100.3:6379[2]> lpush cityname:china ShangHai BeiJing GuangZhou
(integer) 3
L 表示您往链表 head 的左侧插入,插入位置如下所示:
----
GuangZhou BeiJing Shanghai |head|
----
# 您可以使用 `lrange` 命令查看顺序:
192.168.100.3:6379[2]> lrange cityname:china 0 -1
1) "GuangZhou"
2) "BeiJing"
3) "ShangHai"
# 如果使用 `rpush` 命令插入数据,表示往链表 head 右侧插入数据。再使用 `lrange` 查看顺序:
192.168.100.3:6379[2]> rpush cityid:china 35270 46950 12045
(integer) 3
192.168.100.3:6379[2]> LRANGE cityid:china 0 -1
1) "35270"
2) "46950"
3) "12045"
llen
命令
查看单个 key 当中 list 数据类型的元素数量
192.168.100.3:6379[2]> llen cityname:china
(integer) 3
lrange
命令
指定范围获取 list 内的元素。按照从左到右的位置规则,0 表示第一个元素,1 表示第二个元素,以此类推;按照从右到左的位置规则,-1 表示倒数的第一个元素,-2 表示倒数第二个元素,以此类推。
192.168.100.3:6379[2]> lrange cityid:china -2 -1
1) "46950"
2) "12045"
lindex
命令
指定一个索引(索引号)获取 list 当中的单个元素。按照从左到右的位置规则,第一个元素的索引为 0,第二个索引为 1,以此类推;按照从右到左的位置规则,第一个元素的索引为 -1,第二个元素为 -2,以此类推。
192.168.100.3:6379[2]> LINDEX cityname:china 0
"GuangZhou"
192.168.100.3:6379[2]> LINDEX cityname:china 1
"BeiJing"
192.168.100.3:6379[2]> LINDEX cityname:china 2
"ShangHai"
# 元素不存在时返回 nil
192.168.100.3:6379[2]> LINDEX cityname:china 3
(nil)
192.168.100.3:6379[2]> LINDEX cityname:china -1
"ShangHai"
192.168.100.3:6379[2]> LINDEX cityname:china -2
"BeiJing"
192.168.100.3:6379[2]> LINDEX cityname:china -3
"GuangZhou"
lset
命令
通过指定一个索引来修改单个元素。
192.168.100.3:6379[2]> lrange cityname:china 0 -1
1) "GuangZhou"
2) "BeiJing"
3) "ShangHai"
192.168.100.3:6379[2]> lindex cityname:china 0
"GuangZhou"
192.168.100.3:6379[2]> lset cityname:china 0 GZ
OK
192.168.100.3:6379[2]> lindex cityname:china 0
"GZ"
lpushx
命令和 rpushx
命令
x 表示仅当 list 存在时,才往里面追加一个或多个元素。
192.168.100.3:6379[2]> rpushx cityname:china ChongQing HangZhou
(integer) 5
192.168.100.3:6379[2]> lrange cityname:china 0 -1
1) "GZ"
2) "BeiJing"
3) "ShangHai"
4) "ChongQing"
5) "HangZhou"
linsert
命令
在一个元素的前面或后面插入一个元素。前面与后面对应的关键字是 before 和 after。比如要在 ChongQing 元素的前面加入 ShenZhen
192.168.100.3:6379[2]> linsert cityname:china before ChongQing ShenZhen
(integer) 6
192.168.100.3:6379[2]> LRANGE cityname:china 0 -1
1) "GZ"
2) "BeiJing"
3) "ShangHai"
4) "ShenZhen"
5) "ChongQing"
6) "HangZhou"
lpop
命令 和 rpop
命令
pop 表示弹出元素(或移除元素)。当 list 剩余的最后一个元素被弹出后,list(key) 就被会删除。
- 当使用
lpop
命令时,count 表示从左到右弹出的元素个数,必须为正整数,不指定 count,表示弹出第一个元素。 - 当使用
rpop
命令时,count 表示从右到左弹出的元素个数,必须为正整数,不指定 count,表示弹出最后面的一个元素。
# 如果不指定 count,则弹出第一个元素
192.168.100.3:6379[2]> lpop cityname:china
"GZ"
192.168.100.3:6379[2]> lrange cityname:china 0 -1
1) "BeiJing"
2) "ShangHai"
3) "ShenZhen"
4) "ChongQing"
5) "HangZhou"
# 将 BeiJing 和 ShangHai 弹出
192.168.100.3:6379[2]> lpop cityname:china 2
1) "BeiJing"
2) "ShangHai"
192.168.100.3:6379[2]> lrange cityname:china 0 -1
1) "ShenZhen"
2) "ChongQing"
3) "HangZhou"
# 将最后面的一个元素弹出
192.168.100.3:6379[2]> rpop cityname:china
"HangZhou"
192.168.100.3:6379[2]> lrange cityname:china 0 -1
11) "ShenZhen"
12) "ChongQing"
# 将剩余的两个元素弹出,list 被删除
192.168.100.3:6379[2]> lpop cityname:china 2
13) "ShenZhen"
14) "ChongQing"
192.168.100.3:6379[2]> keys *
1) "cityid:china"
blpop
命令和 brpop
命令
命令中的 b 表示 blocking。基本用法如下所示:
192.168.100.3:6379[2]> help blpop
BLPOP key [key ...] timeout
summary: Removes and returns the first element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.
since: 2.0.0
group: list
192.168.100.3:6379[2]> help brpop
BRPOP key [key ...] timeout
summary: Removes and returns the last element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.
since: 2.0.0
group: list
blpop
命令:针对 list 数据类型,对 list 第一个元素定义超时时间,当到达了超时时间,第一个元素弹出。当 list 剩余的最后一个元素被弹出后,list(key) 就被会删除。时间单位为秒。brpop
命令:针对 list 数据类型,对 list 最后面的一个元素定义超时时间,当到达了超时时间,最后面的一个元素弹出。当 list 剩余的最后一个元素被弹出后,list(key) 就被会删除。时间单位为秒。
若您给定了多个 key ,则按照给定 key 的先后顺序依次检查 list。
# 添加一些 list 数据
192.168.100.3:6379[2]> rpush name:first zhang wang wu li
(integer) 4
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "zhang"
2) "wang"
3) "wu"
4) "li"
# 使用示例
192.168.100.3:6379[2]> blpop name:first 10
1) "name:first"
2) "zhang"
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "wang"
2) "wu"
3) "li"
192.168.100.3:6379[2]> brpop name:first 15
1) "name:first"
2) "li"
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "wang"
2) "wu"
# 若您针对一个不存在的 key ,则会阻塞 5 秒或者直到发现有 key 存在且有可被弹出的元素:
192.168.100.3:6379[2]> blpop name 5
(nil)
(5.04s)
rpoplpush
命令
针对两个 key 且它们都是 list 数据类型,将源 key(source key) 的最后面一个元素弹出,并将该元素插入到目标 key (destination key)的最左边。
192.168.100.3:6379[2]> keys *
1) "cityid:china"
2) "name:first"
192.168.100.3:6379[2]> rpoplpush name:first cityid:china
"wu"
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "wang"
192.168.100.3:6379[2]> lrange cityid:china 0 -1
1) "wu"
2) "35270"
3) "46950"
4) "12045"
lrem
命令
从 list 中删除元素。若 list 剩余的最后一个元素被删除,list(key) 就被会删除。用法为:
192.168.100.3:6379[2]> help lrem
LREM key count element
summary: Removes elements from a list. Deletes the list if the last element was removed.
since: 1.0.0
group: list
count 的取值有这么几种情况:
- count > 0。表示从左到右进行搜索,删除与用户指定元素相等的元素,删除的数量为 count 指定的数量
- count = 0。表示删除所有与用户指定元素相等的元素。
- count < 0。表示右到左进行搜索,删除与用户指定元素相等的元素,删除的数量为 count 的绝对值
# 追加两个元素
192.168.100.3:6379[2]> rpushx cityid:china wu 35270
(integer) 6
192.168.100.3:6379[2]> lrange cityid:china 0 -1
1) "wu"
2) "35270"
3) "46950"
4) "12045"
5) "wu"
6) "35270"
# 使用 `lrem` 命令
192.168.100.3:6379[2]> lrem cityid:china 1 wu
(integer) 1
192.168.100.3:6379[2]> lrange cityid:china 0 -1
1) "35270"
2) "46950"
3) "12045"
4) "wu"
5) "35270"
192.168.100.3:6379[2]> lrem cityid:china 0 35270
(integer) 2
192.168.100.3:6379[2]> lrange cityid:china 0 -1
1) "46950"
2) "12045"
3) "wu"
ltrim
命令
对 list 中的元素进行修剪,即只保留用户定义的索引区间的元素,删除索引区间之外的任何元素。按照从左到右的位置规则,第一个元素的索引为 0,第二个索引为 1,以此类推;按照从右到左的位置规则,第一个元素的索引为 -1,第二个元素为 -2,以此类推。
# 追加3个元素
192.168.100.3:6379[2]> rpushx name:first cao qian shun yao
(integer) 5
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "wang"
2) "cao"
3) "qian"
4) "shun"
5) "yao"
# 现只想保留 cao、qian、shun这三个元素,则
192.168.100.3:6379[2]> LTRIM name:first 1 3
OK
192.168.100.3:6379[2]> lrange name:first 0 -1
1) "cao"
2) "qian"
3) "shun"