当前位置: 首页 > news >正文

Redis - List

List 列表

list相当于是数组 / 顺序表!但是内部编码并不是,而是更和双端队列配对!

列表类型是用来存储多个有序的字符串,如下图所示,abcde五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储2³² - 1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当队列的角色,在实际开发上有很多应用场景。

列表两端插入和弹出操作

一样的,这里的列表一样是针对value而言的,毕竟Redis中的Key,永远都是StringValue可以是任意的类型!

Redis的下标支持负数下标,我们之前的hash就已经说过了!

列表的获取、删除等操作

列表类型的特点:

第一、列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表。例如要获取上图的第 5 个元素,可以执行lindex user:1:messages 4;或者获取倒数第 1 个元素,执行lindex user:1:messages -1就可以得到元素e

注意:"有序" 的含义,需要根据上下文进行区分!

  • 有的时候,谈到有序,是指升序、降序
  • 有的时候,谈到有序,是指插入顺序固定、位置很关键,此时对应的就是list结构!也就意味着只要把元素位置颠倒、顺序调换,得到的新list和之前的list就不再等价。【这不就是看成站和队列了嘛】

第二、区分获取删除的区别。例如上图中的lrem 1 b,是从列表中把从左数遇到的前 1 个b元素删除,这个操作会导致列表长度从 5 变成 4;但是执行lindex 4只是单纯获取元素,列表长度不会发生任何变化。

第三、列表中的元素允许重复;但像hash这种结构,field字段是不能重复的。例如列表中可以同时包含两个a元素。

列表中允许有重复元素

命令

LPUSH

将一个或者多个元素从左侧放入(头插)list中。

语法:

LPUSH key element [element ...]

命令有效版本:1.0.0 之后时间复杂度:只插入一个元素为 O (1),插入多个元素为 O (N),N 为插入元素个数。返回值:插入后list的长度。

示例:

127.0.0.1:6379> lpush mylist 1 2 3 4 (integer) 4 127.0.0.1:6379> lpush mylist 5 6 7 8 (integer) 8 127.0.0.1:6379> lpush mylist 9 9 (integer) 10

注意:如果Key已经存在,并且Key对应的Value类型不是list,此时lpush命令就会报错!Redis中的所有的这些各种数据结构的操作,都是类似的效果!


LPUSHX

key存在时,将一个或者多个元素从 左侧放入(头插)到list中。不存在,直接返回

语法:

LPUSHX key element [element ...]

命令有效版本:2.0.0 之后时间复杂度:只插入一个元素为 O (1),插入多个元素为 O (N),N 为插入元素个数。返回值:插入后list的长度。

示例:

127.0.0.1:6379> lpushx key 10 11 12 (integer) 0 127.0.0.1:6379> lpushx mylist 10 11 12 (integer) 16 127.0.0.1:6379> lrange mylist 0 -1 1) "12" 2) "11" 3) "10" 4) "12" 5) "11" 6) "10" 7) "9" 8) "9" 9) "8" 10) "7" 11) "6" 12) "5" 13) "4" 14) "3" 15) "2" 16) "1"

RPUSH

将一个或者多个元素从右侧放入(尾插)list中。

语法:

RPUSH key element [element ...]

命令有效版本:1.0.0 之后时间复杂度:只插入一个元素为 O (1),插入多个元素为 O (N),N 为插入元素个数。返回值:插入后list的长度。

示例:

127.0.0.1:6379> rpush mylist 0 -1 -2 (integer) 19 127.0.0.1:6379> lrange mylist 0 -1 1) "12" 2) "11" 3) "10" 4) "12" 5) "11" 6) "10" 7) "9" 8) "9" 9) "8" 10) "7" 11) "6" 12) "5" 13) "4" 14) "3" 15) "2" 16) "1" 17) "0" 18) "-1" 19) "-2"

RPUSHX

key存在时,将一个或者多个元素从右侧放入(尾插)到list中。

语法:

RPUSHX key element [element ...]

命令有效版本:2.0.0 之后时间复杂度:只插入一个元素为 O (1),插入多个元素为 O (N),N 为插入元素个数。返回值:插入后list的长度。

示例:

127.0.0.1:6379> rpushx key -3 -4 (integer) 0 127.0.0.1:6379> rpushx mylist -3 -4 (integer) 21 127.0.0.1:6379> lrange mylist 0 -1 1) "12" 2) "11" 3) "10" 4) "12" 5) "11" 6) "10" 7) "9" 8) "9" 9) "8" 10) "7" 11) "6" 12) "5" 13) "4" 14) "3" 15) "2" 16) "1" 17) "0" 18) "-1" 19) "-2" 20) "-3" 21) "-4"

LRANGE

LRANGE == list range != left range, 是获取从startend区间的所有元素,左闭右闭

语法:

LRANGE key start stop

命令有效版本:1.0.0 之后时间复杂度:O (N)返回值:指定区间的元素。

示例:

127.0.0.1:6379> lrange mylist 0 9 1) "9" 2) "9" 3) "8" 4) "7" 5) "6" 6) "5" 7) "4" 8) "3" 9) "2" 10) "1" 127.0.0.1:6379> lrange mylist -1 -2 (empty array) 127.0.0.1:6379> lrange mylist -2 -1 1) "2" 2) "1" 127.0.0.1:6379> lrange mylist 0 -1 1) "9" 2) "9" 3) "8" 4) "7" 5) "6" 6) "5" 7) "4" 8) "3" 9) "2" 10) "1"

注意:下标超出范围,也就是越界,是没事的,不像 C++ 越界就是未定义行为!而是尽可能给出!此处对于越界下标的处理方式,更接近于Python的处理方式 ——Python的切片!


LPOP

list左侧取出元素(即头删)

语法:

LPOP key

命令有效版本:1.0.0 之后时间复杂度:O (1)返回值:取出的元素或者nil

示例:

127.0.0.1:6379> lrange mylist 0 -1 1) "12" 2) "11" 3) "10" 4) "12" 5) "11" 6) "10" 7) "9" 8) "9" 9) "8" 10) "7" 11) "6" 12) "5" 13) "4" 14) "3" 15) "2" 16) "1" 17) "0" 18) "-1" 19) "-2" 20) "-3" 21) "-4" 127.0.0.1:6379> lpop mylist "12" 127.0.0.1:6379> lpop mylis (nil)

RPOP

list右侧取出元素(即尾删)

语法:

RPOP key [count]

命令有效版本:1.0.0 之后时间复杂度:O (1)返回值:取出的元素或者nil

LPOPRPOP在当前的Redis 5版本中,都是没有count参数的!从Redis 6.2版本,新增了一个count参数!这里的count就表示要头删尾删的个数!

示例:

127.0.0.1:6379> rpop mylist "-4" 127.0.0.1:6379> rpop mylist "-3" 127.0.0.1:6379> rpop mylis (nil)

LINDEX

获取从左数第index位置的元素。

语法:

LINDEX key index

命令有效版本:1.0.0 之后时间复杂度:O (N)返回值:取出的元素或者nil

示例:

127.0.0.1:6379> lrange mylist 0 -1 1) "11" 2) "10" 3) "12" 4) "11" 5) "10" 6) "9" 7) "9" 8) "8" 9) "7" 10) "6" 11) "5" 12) "4" 13) "3" 14) "2" 15) "1" 16) "0" 17) "-1" 18) "-2" 127.0.0.1:6379> lindex mylist 0 "11" 127.0.0.1:6379> lindex mylist 134 (nil) 127.0.0.1:6379> lindex mylist 12 "3" 127.0.0.1:6379> lindex mylis 12 (nil)

LINSERT

在特定位置插入元素。

语法:

LINSERT key <BEFORE | AFTER> pivot element

命令有效版本:2.2.0 之后时间复杂度:O (N)返回值:插入后的list长度。

示例:

127.0.0.1:6379> lrange mylist 0 -1 1) "6" 2) "5" 3) "4" 4) "3" 5) "2" 6) "1" 127.0.0.1:6379> linsert mylist after 4 100 (integer) 7 127.0.0.1:6379> lrange mylist 0 -1 1) "6" 2) "5" 3) "4" 4) "100" 5) "3" 6) "2" 7) "1" 127.0.0.1:6379> linsert mylist before 4 200 (integer) 8 127.0.0.1:6379> lrange mylist 0 -1 1) "6" 2) "5" 3) "200" 4) "4" 5) "100" 6) "3" 7) "2" 8) "1" 127.0.0.1:6379> linsert mylit before 4 200 (integer) 0

LLEN

获取list长度。

语法:

LLEN key

命令有效版本:1.0.0 之后时间复杂度:O (1)返回值:list的长度。

示例:

127.0.0.1:6379> llen mylist (integer) 8 127.0.0.1:6379> llen mylis (integer) 0

LREM

移除列表中的元素。

语法:

LREM key count element

count —— 要删除的个数

element —— 要删除的值

命令有效版本:1.0.0 之后时间复杂度:O (n),其中 n 是列表的长度。返回值:被移除元素的数量。

RedisLREM命令中,count参数指定了要移除元素的数量。这个参数可以是正数、负数或零,其含义如下:

列表:[A, B, A, C, A]

  • LREM list 2 A→ 从左删 2 个 A → 剩[B, C, A]
  • LREM list -2 A→ 从右删 2 个 A → 剩[A, B, C]
  • LREM list 0 A→ 删掉所有 A → 剩[B, C]

示例:

127.0.0.1:6379> lpush mylist 1 2 3 4 1 2 3 4 (integer) 8 127.0.0.1:6379> lrange mylist 0 -1 1) "4" 2) "3" 3) "2" 4) "1" 5) "4" 6) "3" 7) "2" 8) "1" 127.0.0.1:6379> lrem mylist -1 2 (integer) 1 127.0.0.1:6379> lrange mylist 0 -1 1) "4" 2) "3" 3) "2" 4) "1" 5) "4" 6) "3" 7) "1" 127.0.0.1:6379> lrem mylist 1 2 (integer) 1 127.0.0.1:6379> lrange mylist 0 -1 1) "4" 2) "3" 3) "1" 4) "4" 5) "3" 6) "1" 127.0.0.1:6379> lrem mylist 0 3 (integer) 2 127.0.0.1:6379> lrange mylist 0 -1 1) "4" 2) "1" 3) "4" 4) "1"

在这个示例中,LREM命令从mylist列表中移除了最后一个匹配的"value2"元素。


LTRIM

修剪列表,只保留指定区间的元素。

语法:

LTRIM key start stop

命令有效版本:1.0.0 之后时间复杂度:O (n),其中 n 是被移除元素的数量。返回值:OK。

示例:

127.0.0.1:6379> lpush mylist "value1" "value2" "value3" "value4" "value5" (integer) 5 127.0.0.1:6379> ltrim mylist 1 3 OK 127.0.0.1:6379> lrange mylist 0 -1 1) "value2" 2) "value3" 3) "value4"

在这个示例中,LTRIM命令修剪了mylist列表,只保留了索引 1 到 3(包含)的元素。


LSET

设置列表指定索引的值。

语法:

LSET key index value

命令有效版本:1.0.0 之后时间复杂度:O (n),其中 n 是索引位置之前的元素数量。返回值:OK。

示例:

127.0.0.1:6379> lpush mylist "value1" "value2" "value3" (integer) 3 127.0.0.1:6379> lset mylist 1 "newValue" OK 127.0.0.1:6379> lrange mylist 0 -1 1) "value1" 2) "newValue" 3) "value3"

在这个示例中,LSET命令将mylist列表中索引 1 的元素设置为"newValue"

阻塞版本命令

b---block(阻塞)

blpopbrpoplpoprpop阻塞版本,和对应非阻塞版本的作用基本一致,除了:

在列表中有元素的情况下,阻塞和非阻塞表现是一致的。但如果列表中没有元素,非阻塞版本会立即返回nil,但阻塞版本会根据timeout阻塞一段时间,期间Redis可以执行其他命令,但要求执行该命令的客户端会表现为阻塞状态。

命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。(blpopbrpop都是可以同时去尝试获取多个key的列表的元素的,多个key对应多个list,这多个list哪个有元素了,就会返回哪一个元素)

如果多个客户端同时对一个键执行pop,则最先执行命令的客户端会得到弹出的元素。

阻塞版本的blpop和非阻塞版本lpop的区别

BLPOP

LPOP的阻塞版本。

语法:

BLPOP key [key ...] timeout

可以指定一个或者多个key,每一个key都对应一个list

如果这些list有任何一个为非空,blpop都能够把里面的元素获取到,立即返回。

如果这些list都为空,此时就需要阻塞等待,等待其他客户端往这些list插入元素。

此处还可以指定超时时间,单位为秒!(Redis 6之后,可以设置小数)

命令有效版本:1.0.0 之后时间复杂度:O (1)返回值:取出的元素或者nil

示例:

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

RPOP的阻塞版本。

语法:

BRPOP key [key ...] timeout

命令有效版本:1.0.0 之后时间复杂度:O (1)返回值:取出的元素或者nil

示例:

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

命令小结

有关列表的命令已经介绍完毕,下表是这些命令的作用和时间复杂度,开发人员可以参考。

列表命令

操作类型命令时间复杂度
添加rpush key value [value ...]O(k),k 是元素个数
添加lpush key value [value ...]O(k),k 是元素个数
添加lpush key before | after pivot valueO(n),n 是 pivot 距离头尾的距离
查找lrange key start en dO(s+n),s 是 start 偏移量,n 是 start 到 end 的范围
查找lindex key indexO(n),n 是索引的偏移量
查找llen keyO(1)
删除lpop keyO(1)
删除rpop keyO(1)
删除lrem key count valueO(k),k 是元素个数
删除ltrim key start endO(k),k 是元素个数
修改lset key index valueO(n),n 是索引的偏移量
阻塞操作blpop brpopO(1)

内部编码

列表类型的内部编码有两种:

ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认 512 个),同时列表中每个元素的长度都小于list-max-ziplist-value配置(默认 64 字节)时,Redis 会选择用ziplist来作为列表的内部编码实现,以此减少内存消耗。(节省空间,元素多了之后操作效率偏低)

linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis 会使用linkedlist作为列表的内部实现。

上面这两种编码方式是 Redis 旧版本的编码方式,现在新版本的编码方式是以quicklist数据结构来实现的,它是ziplistlinkedlist的结合体!整体还是一个双向链表结构,但是链表的每一个节点都是一个压缩列表ziplist

旧版本设计规则:

当元素个数较少且没有超大元素时,内部编码为ziplist

127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist"

当元素个数超过 512 个时,内部编码转为linkedlist

127.0.0.1:6379> rpush listkey e1 e2 e3 ... 省略 e512 e513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist"

当某个元素的长度超过 64 字节时,内部编码也会转为linkedlist

127.0.0.1:6379> rpush listkey "one string is bigger than 64 bytes ... 省略 ..." (integer) 1 127.0.0.1:6379> object encoding listkey "linkedlist"

新版本现在的设计:

127.0.0.1:6379> lpush mylist 1 2 3 4 5 6 (integer) 6 127.0.0.1:6379> type mylist list 127.0.0.1:6379> object encoding mylist "quicklist"

使用场景

消息队列

如下图所示,Redis 可以使用lpush + brpop命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式地从队列中“争抢”队首元素。通过多个客户端来保证消费的负载均衡和高可用性。

Redis 阻塞消息队列模型

分频道的消息队列

如下图所示,Redis 同样使用lpush + brpop命令,但通过不同的键模拟频道的概念,不同的消费者可以通过brpop不同的键值,实现订阅不同频道的理念。

Redis 分频道阻塞消息队列模型

多个列表/频道,这种场景是非常常见的,日常使用的一些程序,比如说:抖音

有一些通道是用来传输端短视频数据的,还有一个通道是用来传输弹幕的!

...

搞成多个频道,就可以在某种数据发生问题的时候,不会对其他频道产生影响,解耦合!

微博 Timeline

每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

每篇微博使用哈希结构存储,例如微博中 3 个属性:title、timestamp、content(标题,时间,内容)

hmset mblog:1 title xx timestamp 1476536196 content xxxx ... hmset mblog:n title xx timestamp 1476536196 content xxxxx

向用户 Timeline 添加微博,user:<uid

lpush user:1:mblogs mblog:1 mblog:3 ... lpush user:k:mblogs mblog:9

分页获取用户的 Timeline,例如获取用户 1 的前 10 篇微博

keylist = lrange user:1:mblogs 0 9 for key in keylist { hgetall key }

此方案在实际中可能存在两个问题:

  1. 1+n 问题。即如果每次分页获取的微博个数较多,需要执行多次hgetall操作,此时可以考虑使用 pipeline(流水线)模式(虽然咱们是多个 redis 命令,但是把这些命令合并成一个网络请求进行通信)批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类型,使用mget获取。

  2. 分割获取文章时lrange在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。

💡选择列表类型时,请参考:

同侧存取(lpush+lpop或者rpush+rpop)为栈
异侧存取(lpush+rpop或者rpush+lpop)为队列

http://www.jsqmd.com/news/733989/

相关文章:

  • XGP存档提取技术解析:架构设计与跨平台迁移实战指南
  • 预推免线下复试全记录:从华工、暨大到湖大,我的‘赶考’日程与面试踩坑复盘
  • 如何免费解锁Cursor Pro功能?cursor-free-vip工具完整使用指南
  • 你知道吗?其实这些都是AI——智能交通管理系统
  • DroidPlugin性能优化:提升插件应用运行效率的10个关键技巧
  • YOLOv13涨点改进| TGRS 2026 |全网独家创新、注意力改进篇| 引入PMM 金字塔掩码Mamba模块,逐步整合深层语义信息与浅层细节信息,含多种改进,助力小目标检测、图像分割高效涨点
  • C++高频面试题总结(一)
  • MCP 2026多租户隔离能力深度评测(2026 Q1权威基准测试报告首发):98.7%租户间资源泄露拦截率如何达成?
  • 内存碎片是内存分配和释放过程中导致可用内存分散成不连续的小块,从而降低内存使用效率或引发分配失败的问题
  • PPTAgent终极指南:5分钟掌握AI智能演示文稿生成
  • 移动应用开发手册13:环境治理——本地/测试/生产分不清
  • Arduino串口通信避坑大全:从Serial.read丢数据到parseFloat的诡异行为,一次讲清
  • MPC-BE:你的Windows电脑需要一个什么样的播放器?5个场景告诉你答案
  • SUSI.AI社区贡献指南:如何参与开源AI项目开发
  • 在模型广场中根据任务需求与预算快速对比并选择合适的大模型
  • Midscene.js视觉AI自动化实战指南:10个技巧实现跨平台UI自动化
  • icestark实战案例解析:电商平台微前端架构演进之路
  • 终极指南:如何高效序列化与部署Thinc深度学习模型到生产环境
  • 一文读懂如何修改浏览器头像(附实操教程)
  • 告别暴力堆叠空洞卷积:手把手解读DWRSeg如何用‘两步走’策略,在Cityscapes上跑出319.5 FPS
  • SUSI.AI完整指南:10个技巧让AI助手更懂你
  • 终极指南:如何轻松重置JetBrains IDE试用期,告别30天限制烦恼!
  • Baby Dragon Hatchling (BDH)未来路线图:下一代类脑AI架构的5大发展方向
  • 3个技巧让你的Windows任务栏焕然一新:TranslucentTB完全指南
  • 3步解锁游戏无限可能:零门槛ASI模组加载器完全指南
  • 从裸机到FreeRTOS:手把手教你重构DHT11温湿度采集任务(附中断优先级避坑指南)
  • 7步精通GSE宏编译器:从零构建魔兽世界技能自动化的完整指南
  • 终极指南:PDFMathTranslate证书验证问题的完整解决方案
  • 别再傻傻分不清了!LTS、Beta、Dev这些版本号到底该用哪个?附选型指南
  • 如何零基础掌握WPR机器人仿真:从安装到实战的完整指南