Redis数据类型之list数据类型与结构
1、list数据类型
list类型是用来存储多个有序的字符串的,列表当中的每一个字符看做一个元素,一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素。redis可以从列表的两端进行插入(push)和弹出(pop)元素,支持读取指定范围的元素集,或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色。
redis列表是链表型的数据结构,所以它的元素是有序的,而且列表内的元素是可以重复的。意味着它可以根据链表的下标获取指定的元素和某个范围内的元素集。
2、list命令
2.1、lpush命令
LPUSH key element [element ...]
将一个或多个值 element 插入到列表 key 的表头
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
2.2、lpushx命令
LPUSHX key element [element ...]
将值 element 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。
2.3、rpush命令
RPUSH key element [element ...]
将一个或多个值 element 插入到列表 key 的表尾(最右边)。
如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
2.4、rpushx命令
RPUSHX key element [element ...]
将值 element 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。
2.5、lpop命令
LPOP key
移除并返回列表 key 的头元素。
返回值
列表的头元素。 当 key 不存在时,返回 nil 。
2.6、rpop命令
RPOP key
移除并返回列表 key 的尾元素。
返回值
列表的尾元素。 当 key 不存在时,返回 nil 。
2.7、rpoplpush命令
RPOPLPUSH source destination
命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
-
将列表
source中的最后一个元素(尾元素)弹出,并返回给客户端。 -
将
source弹出的元素插入到列表destination,作为destination列表的的头元素。
如果 source 不存在,值 nil 被返回,并且不执行其他动作。
如果 source 和 destination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
2.8、lrem命令
LREM key count element
根据参数 count 的值,移除列表中与参数 value 相等的元素。
count 的值可以是以下几种:
-
count > 0: 从表头开始向表尾搜索,移除与value相等的元素,数量为count。 -
count < 0: 从表尾开始向表头搜索,移除与value相等的元素,数量为count的绝对值。 -
count = 0: 移除表中所有与value相等的值。
返回值
被移除元素的数量。 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。
2.9、llen命令
LLEN key
返回列表 key 的长度。
如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
如果 key 不是列表类型,返回一个错误。
2.10、lindex命令
LINDEX key index
返回列表 key 中,下标为 index 的元素。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
如果 key 不是列表类型,返回一个错误。
返回值
列表中下标为 index 的元素。 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil 。
2.11、linsert命令
LINSERT key BEFORE|AFTER pivot element
将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
当 pivot 不存在于列表 key 时,不执行任何操作。
当 key 不存在时, key 被视为空列表,不执行任何操作。
如果 key 不是列表类型,返回一个错误。
返回值
如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 pivot ,返回 -1 。 如果 key 不存在或为空列表,返回 0 。
2.12、lset命令
LSET key index element
将列表 key 下标为 index 的元素的值设置为 element。
当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
2.13、lrange命令
LRANGE key start stop
返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
超出范围的下标
超出范围的下标值不会引起错误。
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,那么 LRANGE 返回一个空列表。
如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end 。
2.14、ltrim 命令
LTRIM key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
当 key 不是列表类型时,返回一个错误。
超出范围的下标
超出范围的下标值不会引起错误。
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stop , LTRIM 返回一个空列表(因为 LTRIM 已经将整个列表清空)。
如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end 。
2.15、blpop 命令
BLPOP key [key ...] timeout
BLPOP 是列表的阻塞式(blocking)弹出
2.16、brpop 命令
BRPOP key [key ...] timeout
BRPOP 是列表的阻塞式(blocking)弹出
2.17、brpoplpush 命令
BRPOPLPUSH source destination timeout
BRPOPLPUSH 是 RPOPLPUSH source destination 的阻塞版本
3、List底层结构的实现
Redis 5.0.9 以后List类型结构快速列表(quicklist)
quicklist是依赖于压缩列表(ziplist)和链表(linkedlist)来实现的,它是两个结构的结合。将ziplist来进行分段存储,也就是分成一个个的quicklistNode节点来进行存储。每个quicklistNode指向一个ziplist,然后quicklistNode之间是通过双向指针来进行连接的。

为什么要把底层数据结构优化成quicklist?
从两个方面考虑:
ziplist这个结构,它内部的数据存储是一段连续的空间,这样的话,就要求很大一块内存空间。就比如说,我们想存储很多的数据,但是内存中并没有符合要求的连续的存储空间,而是存在很多不连续的小空间(加起来可以符合要求)。
再来说说linkedlist这个结构,它数据存储不要求连续,就可以避免上面的弊端,不过这样以来,每个节点都分配一个一块内存,那就有可能造成大量的内存碎片。
综上两个考虑,redis在3.2版本以后就对此情况进行了优化,就出来了quicklist这个数据结构,quicklist其实就是一个分段的ziplist,为什么这么说呢?其实quicklist存储数据基本单位是quicklistNode,每个quicklistNode的内容区就是以ziplist数据结构存储的。也就是上面图示展示的。

- Zlbytes:压缩列表占用的内存字节数
- Zltail:压缩列表尾结点距离起始地址有多少字节
- Zllen:压缩列表节点数
- entryX:压缩列表节点
- Zlend:压缩列表末端
- Previous_entry_length:压缩列表前一个节点的长度
- Encoding:当前节点的值的类型与长度
- Content:当前节点的值

4、List类型应用场景
4.1、队列-秒杀抢购
list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,故而可以用Redis的list类型实现简单的点对点的消息队列。不过我不推荐在实战中这么使用,因为现在已经有Kafka、NSQ、RabbitMQ等成熟的消息队列了,它们的功能已经很完善了,除非是为了更深入地理解消息队列,不然我觉得没必要去重复造轮子。
4.2、队列-排行榜
list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等。
但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,之后在介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。


浙公网安备 33010602011771号