3.redis数据结构之List

List-列表类型:L&R

列表类型:有序、可重复

Arraylist和linkedlist的区别

Arraylist是使用数组来存储数据,特点:查询快、增删慢

Linkedlist是使用双向链表存储数据,特点:增删快、查询慢,但是查询链表两端的数据也很快。

Redis的list是采用来链表来存储的,所以对于redis的list数据类型的操作,是操作list的两端数据来操作的。

命令

向列表左边增加元素:lpush key:val1 val2

127.0.0.1:6379>lpush list:1 1 2 3 (integer) 3

向列表右边增加元素:rpush key:val1 val2

127.0.0.1:6379>rpush list:1 4 5 6 (integer) 3

查看列表:LRANGE key start stop

LRANGE命令是列表类型最常用的命令之一,获取列表中的某一片段,将返回start、stop之间的所有元素(包含两端的元素),索引从0开始。索引可以是负数,如:"-1"代表最后边的一个元素。

语法:LRANGE key start stop 获取下标0-2的元素 前闭后闭 127.0.0.1:6379> lrange list1 0 2 1) "2" 2) "1" 3) "4" ​ 这里需要注意的是,list的索引从0开始大家知道,但是取全部list的值时,结束索引需要设置为-1,也可以设置成list长度减1 127.0.0.1:6379> lrange l:list 0 -1 1) "6" 2) "5" 3) "2" 4) "2"

从列表两端弹出元素:LPOP key&RPOP key

LPOP命令从列表左边弹出一个元素,会分两步完成:

第一步是将列表左边的元素从列表中移除

第二步是返回被移除的元素值。

语法: LPOP key RPOP key 127.0.0.1:6379> lpop list:1 "3" 127.0.0.1:6379> rpop list:1 "6"

获取列表中元素的个数LLEN key

语法:LLEN key 127.0.0.1:6379> llen list:1 (integer) 2

删除列表中指定的值 LREM key count value

LREM命令会删除列表中前count个值为value的元素,返回实际删除的元素个数。根据count值的不同,该命令的执行方式会有所不同: 当count>0时, LREM会从列表左边开始删除。 当count<0时, LREM会从列表后边开始删除。 当count=0时, LREM删除所有值为value的元素。 删除2个值为1的元素 语法:LREM key count value 127.0.0.1:6379> lrem list1 2 1 (integer) 2

获得/设置指定索引的元素值LINDEX key index

获得指定索引的元素值 语法:LINDEX key index 127.0.0.1:6379> lindex list 2 "1" ​ 设置指定索引的元素值 语法:LSET key index value 127.0.0.1:6379> lset list 2 2 OK

只保留列表指定范围元素:LTRIM key start stop

只保留start - stop 之间的元素 语法:LTRIM key start stop 127.0.0.1:6379> lrange l:list 0 -1 1) "6" 2) "5" 3) "0" 4) "2" 127.0.0.1:6379> ltrim l:list 0 2 OK 127.0.0.1:6379> lrange l:list 0 -1 1) "6" 2) "5" 3) "0"

向列表中插入元素LINSERT key BEFORE|AFTER pivot value

该命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。 ​ 语法:LINSERT key BEFORE|AFTER pivot value 127.0.0.1:6379> lrange list 0 -1 1) "3" 2) "2" 3) "1" ​ 将值为4的元素插入到3后面 127.0.0.1:6379> linsert list after 3 4 (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "3" 2) "4" 3) "2" 4) "1"

将A列表最后一个元素移到B列表RPOPLPUSH source destination

用于移除列表的最后一个元素,并将该元素添加到另一个列表并返回。

语法:RPOPLPUSH source destination ​ 127.0.0.1:6379> lrange list 0 -1 1) "3" 2) "4" 3) "2" 4) "1" 127.0.0.1:6379> rpoplpush list newlist "1" 127.0.0.1:6379> lrange newlist 0 -1 1) "1" 127.0.0.1:6379> lrange list 0 -1 1) "3" 2) "4" 3) "2"

移出并获取列表的最后一个元素(阻塞):Brpop

移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

redis 127.0.0.1:6379> BRPOP LIST1 LIST2 .. LISTN TIMEOUT

假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。

redis> DEL list1 list2 (integer) 0 redis> RPUSH list1 a b c (integer) 3 redis> BRPOP list1 list2 0 1) "list1" 2) "c"

将A列表最后一个元素移到B列表(阻塞)Brpoplpush

命令从列表中取出最后一个元素,并插入到另外一个列表的头部; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。

``` redis 127.0.0.1:6379> BRPOPLPUSH LIST1 ANOTHER_LIST TIMEOUT

非空列表

​ 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) ```

应用场景:大容量&更新频率不高&的静态列表

商品评论列表

思路

在Redis中创建商品评论列表

用户发布商品评论,将评论信息转成json存储到list中。

用户在页面查询评论列表,从redis中取出json数据展示到页面。

定义商品评论列表key

商品编号为1001的商品评论key【items: comment:1001】

每日排行榜:某个时间段数据固定榜单

list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等,下图是酷狗音乐"K歌擂台赛"的昨日打擂金曲排行榜,每日计算一次,存储在list类型中,接口访问时,通过page和size分页获取打擂金曲。

list类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。 ​   但是,并不是所有的最新列表都能用list类型实现,因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉,举个例子,当前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(E,D,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(C,B,A),元素C重复了。可以尾插头拿 即 左边插入从右边拿。   只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用list类型实现。   对于需要分页并且会频繁更新的列表,需用使用有序集合sorted set类型实现。   另外,需要通过时间范围查找的最新列表,list类型也实现不了,也需要通过有序集合sorted set类型实现,如以成交时间范围作为条件来查询的订单列表。之后在介绍有序集合sorted set类型的应用场景时会详细介绍sorted set类型如何实现最新列表。 ​   那么问题来了,对于排行榜和最新列表两种应用场景,list类型能做到的sorted set类型都能做到,list类型做不到的sorted set类型也能做到,那为什么还要使用list类型去实现排行榜或最新列表呢,直接用sorted set类型不是更好吗?   原因是sorted set类型占用的内存容量是list类型的数倍之多(之后会在容量章节详细介绍),对于列表数量不多的情况,可以用sorted set类型来实现,比如上文中举例的打擂金曲排行榜,每天全国只有一份,两种数据类型的内存容量差距可以忽略不计,但是如果要实现某首歌曲的翻唱作品地区排行榜,数百万的歌曲,300多个地区,会产生数量庞大的榜单,或者数量更加庞大的朋友圈点赞列表,就需要慎重地考虑容量的问题了。

底层数据结构压缩列表和双向链表


在版本3.2之前,Redis 列表list使用两种数据结构作为底层实现:

  • 压缩列表ziplist
  • 双向链表linkedlist
  • 因为双向链表占用的内存比压缩列表要多, 所以当创建新的列表键时, 列表会优先考虑使用压缩列表, 并且在有需要的时候, 才从压缩列表实现转换到双向链表实现。

ziplist

ziplist 是一个特殊的双向链表 特殊之处在于:没有维护双向指针:prev next;而是存储上一个 entry的长度和 当前entry的长度,通过长度推算下一个元素在什么地方。 牺牲读取的性能,获得高效的存储空间,因为(简短字符串的情况)存储指针比存储entry长度 更费内存。这是典型的"时间换空间"。

ziplist使用局限性 字段、值比较小,才会用ziplist。

压缩列表ziplist存储结构 ziplist使用连续的内存块,每一个节点(entry)都是连续存储的;

1.ziplist可以通过data header计算出当前entry的结束位置,也就能得到下一个entry的起始位置,正向遍历

2.ziplist可以通过length of previous entry计算出上一个entry的起始位置,反向遍历

压缩列表转化成双向链表条件

创建新列表时 redis 默认使用 redisencoding ziplist 编码, 当以下任意一个条件被满足时, 列表会被转换成 redisencodinglinkedlist 编码:

  • 试图往列表新添加一个字符串值,且这个字符串的长度超过 server.listmaxziplist_value (默认值为 64字节)。
  • ziplist 包含的节点超过 server.listmaxziplist_entries (默认值为 512 )。

注意:这两个条件是可以修改的,在 redis.conf 中:

list-max-ziplist-value 64 list-max-ziplist-entries 512

linkedlist

当链表entry数据超过512、或单个value 长度超过64字节,底层就会转化成linkedlist编码; linkedlist是标准的双向链表,Node节点包含prev和next指针,可以进行双向遍历; 还保存了 head 和 tail 两个指针,因此,对链表的表头和表尾进行插入的复杂度都为 (1) ------ 这是高效实现 LPUSH 、 RPOP、 RPOPLPUSH 等命令的关键。

相关推荐
未来之窗软件服务7 分钟前
sql速度优化多条合并为一条语句
数据库
山东布谷科技官方9 分钟前
布谷直播源码部署服务器关于数据库配置的详细说明
运维·服务器·数据库·直播系统源码·直播源码·直播系统搭建·直播软件开发
易云码30 分钟前
信息安全建设方案,网络安全等保测评方案,等保技术解决方案,等保总体实施方案(Word原件)
数据库·物联网·安全·web安全·低代码
newxtc35 分钟前
【客观理性深入讨论国产中间件及数据库-科创基础软件】
数据库·中间件·国产数据库·国产中间件·科创
水月梦镜花38 分钟前
redis:list列表命令和内部编码
数据库·redis·list
泉崎1 小时前
11.7比赛总结
数据结构·算法
你好helloworld1 小时前
滑动窗口最大值
数据结构·算法·leetcode
MonkeyKing_sunyuhua1 小时前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
天郁青1 小时前
数据库交互的本地项目:后台管理系统
数据库·交互
零意@1 小时前
ubuntu切换不同版本的python
windows·python·ubuntu