内容简介:列表(列表是一种比较如图所示,
列表( list )类型是用来存储多个 有序 的 字符串 。在 Redis 中,可以对列表的 两端 进行 插入 ( push )和 弹出 ( pop )操作,还可以获取 指定范围 的 元素列表 、获取 指定索引下标 的 元素 等。
列表是一种比较 灵活 的 数据结构 ,它可以充当 栈 和 队列 的角色,在实际开发上有很多应用场景。
如图所示, a 、 b 、 c 、 d 、 e 五个元素 从左到右 组成了一个 有序的列表 ,列表中的每个字符串称为 元素 ( element ),一个列表最多可以存储 2 ^ 32 - 1 个元素。
- 列表的 插入 和 弹出 操作
- 列表的 获取 、 截取 和 删除 操作
正文
1. 相关命令
下面将按照对 列表 的 5 种 操作类型 对命令进行介绍:
1.1. 添加命令
1.1.1. 从右边插入元素
rpush key value [value ...]
下面代码 从右向左 插入元素 c 、 b 、 a :
127.0.0.1:6379> rpush listkey c b a (integer) 3 复制代码
lrange 0 -1 命令可以 从左到右 获取列表的 所有元素 :
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a" 复制代码
1.1.2. 从左边插入元素
lpush key value [value ...]
使用方法和 rpush 相同,只不过从 左侧插入 ,这里不再赘述。
1.1.3. 向某个元素前或者后插入元素
linsert key before|after pivot value
linsert 命令会从 列表 中找到 第一个 等于 pivot 的元素,在其 前 ( before )或者 后 ( after )插入一个新的元素 value ,例如下面操作会在列表的 元素 b 前插入 redis :
127.0.0.1:6379> linsert listkey before b redis (integer) 4 复制代码
返回结果为 4 ,代表当前 列表 的 长度 ,当前列表变为:
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b" 4) "a" 复制代码
1.2. 查询命令
1.2.1. 获取指定范围内的元素列表
lrange key start stop
lrange 操作会获取列表 指定索引 范围所有的元素。
索引下标有两个特点:
-
其一,索引下标 从左到右 分别是
0到N-1,但是 从右到左 分别是-1到-N。 -
其二,
lrange中的end选项包含了 自身 ,这个和很多编程语言不包含end不太相同。
从左到右获取列表的第 2 到第 4 个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey 1 3 1) "redis" 2) "b" 3) "a" 复制代码
从右到左获取列表的第 1 到第 3 个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey -3 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.2.2. 获取列表指定索引下标的元素
lindex key index
例如当前列表 最后一个 元素为 a :
127.0.0.1:6379> lindex listkey -1 "a" 复制代码
1.2.3. 获取列表长度
llen key
例如,下面示例 当前列表长度 为 4 :
127.0.0.1:6379> llen listkey (integer) 4 复制代码
1.3. 删除命令
1.3.1. 从列表左侧弹出元素
lpop key
如下操作将 列表 最左侧的元素 c 弹出,弹出后 列表 变为 redis 、 b 、 a 。
127.0.0.1:6379> lpop listkey "c" 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.3.2. 从列表右侧弹出元素
rpop key
它的使用方法和 lpop 是一样的,只不过从列表 右侧 弹出元元素。
127.0.0.1:6379> lpop listkey "a" 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b" 复制代码
1.3.3. 删除指定元素
lrem key count value
lrem 命令会从 列表 中找到 等于 value 的元素进行 删除 ,根据 count 的不同分为三种情况:
-
count > 0: 从左到右 ,删除最多
count个元素。 -
count < 0: 从右到左 ,删除最多
count绝对值 个元素。 -
count = 0, 删除所有 。
例如向列表 从左向右 插入 5 个 a ,那么当前 列表 变为 “a a a a a redis b a” ,下面操作将从列表 左边 开始删除 4 个为 a 的元素:
127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "redis" 3) "b" 4) "a" 复制代码
1.3.4. 按照索引范围修剪列表
127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.4. 修改命令
1.4.1. 修改指定索引下标的元素
修改 指定索引下标 的元素:
lset key index newValue
下面操作会将列表 listkey 中的第 3 个元素设置为 mysql :
127.0.0.1:6379> lset listkey 2 mysql OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "mysql" 复制代码
1.5. 阻塞操作命令
阻塞式弹出操作的命令如下:
blpop key [key ...] timeout brpop key [key ...] timeout
blpop 和 brpop 是 lpop 和 rpop 的 阻塞版本 ,它们除了 弹出方向 不同, 使用方法 基本相同,所以下面以 brpop 命令进行说明, brpop 命令包含两个参数:
-
key[key...]:一个列表的 多个键 。
-
timeout: 阻塞 时间(单位: 秒 )。
对于 timeout 参数,要氛围 列表为空 和 不为空 两种情况:
- 列表为空
如果 timeout = 3 ,那么 客户端 要等到 3 秒后返回,如果 timeout = 0 ,那么 客户端 一直 阻塞 等下去:
127.0.0.1:6379> brpop list:test 3 (nil) (3.10s) 127.0.0.1:6379> brpop list:test 0 ...阻塞... 复制代码
如果此期间添加了数据 element1 ,客户端 立即返回 :
127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s) 复制代码
- 列表不为空 :客户端会 立即返回 。
127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1" 复制代码
在使用 brpop 时,有以下两点需要注意:
- 其一,如果是 多个键 ,那么
brpop会 从左至右 遍历键,一旦有 一个键 能 弹出元素 ,客户端 立即返回 :
127.0.0.1:6379> brpop list:1 list:2 list:3 0 ..阻塞.. 复制代码
此时另一个 客户端 分别向 list:2 和 list:3 插入元素:
client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1 复制代码
客户端会立即返回 list:2 中的 element2 ,因为 list:2 最先有 可以弹出 的元素。
127.0.0.1:6379> brpop list:1 list:2 list:3 0 1) "list:2" 2) "element2" 复制代码
- 其二,如果 多个客户端 对 同一个键 执行
brpop,那么 最先执行brpop命令的 客户端 可以 获取 到弹出的值。
按先后顺序在 3 个客户端执行 brpop 命令:
- 客户端1:
client-1> brpop list:test 0 ...阻塞... 复制代码
- 客户端2:
client-2> brpop list:test 0 ...阻塞... 复制代码
- 客户端3:
client-3> brpop list:test 0 ...阻塞... 复制代码
此时另一个 客户端 lpush 一个元素到 list:test 列表中:
client-lpush> lpush list:test element (integer) 1 复制代码
那么 客户端 1 会获取到元素,因为 客户端 1 最先执行 brpop 命令,而 客户端 2 和 客户端 3 会继续 阻塞 。
client> brpop list:test 0 1) "list:test" 2) "element" 复制代码
有关 列表 的 基础命令 已经介绍完了,下表是相关命令的 时间复杂度 :
2. 内部编码
列表类型的 内部编码 有两种:
2.1. ziplist(压缩列表)
当列表的元素个数 小于 list-max-ziplist-entries 配置(默认 512 个),同时列表中 每个元素 的值都 小于 list-max-ziplist-value 配置时(默认 64 字节), Redis 会选用 ziplist 来作为 列表 的 内部实现 来减少内存的使用。
2.2. linkedlist(链表)
当 列表类型 无法满足 ziplist 的条件时, Redis 会使用 linkedlist 作为 列表 的 内部实现 。
2.3. 编码转换
下面的示例演示了 列表类型 的 内部编码 ,以及相应的变化。
- 当元素 个数较少 且 没有大元素 时, 内部编码 为
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 e4 e5 ... 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 byte..." (integer) 4 127.0.0.1:6379> object encoding listkey "linkedlist" 复制代码
Redis3.2 版本提供了 quicklist 内部编码 ,简单地说它是以一个 ziplist 为 节点 的 linkedlist ,它结合了 ziplist 和 linkedlist 两者的优势,为 列表类型 提供了一种更为优秀的 内部编码 实现,它的设计原理可以参考 Redis 的另一个作者 Matt Stancliff 的博客redis-quicklist。
3. 应用场景
3.1. 消息队列
通过 Redis 的 lpush + brpop 命令组合 ,即可实现 阻塞队列 。如图所示:
生产者客户端使用 lrpush 从列表 左侧插入元素 , 多个消费者客户端 使用 brpop 命令 阻塞式 的 “抢” 列表 尾部 的元素, 多个客户端 保证了消费的 负载均衡 和 高可用性 。
3.2. 文章列表
每个 用户 有属于自己的 文章列表 ,现需要 分页 展示文章列表。此时可以考虑使用 列表 ,因为列表不但是 有序的 ,同时支持 按照索引范围 获取元素。
- 每篇文章使用 哈希结构 存储,例如每篇文章有
3个属性title、timestamp、content:
hmset acticle:1 title xx timestamp 1476536196 content xxxx hmset acticle:2 title yy timestamp 1476536196 content yyyy ... hmset acticle:k title kk timestamp 1476512536 content kkkk 复制代码
- 向用户文章列表 添加文章 ,
user:{id}:articles作为用户文章列表的 键 :
lpush user:1:acticles article:1 article:3 article:5 lpush user:2:acticles article:2 article:4 article:6 ... lpush user:k:acticles article:7 article:8 复制代码
- 分页 获取 用户文章列表 ,例如下面 伪代码 获取用户
id=1的前10篇文章:
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
复制代码
使用 列表 类型 保存 和 获取 文章列表会存在两个问题:
-
第一:如果每次 分页 获取的 文章个数较多 ,需要执行多次
hgetall操作,此时可以考虑使用Pipeline进行 批量获取 ,或者考虑将文章数据 序列化为字符串 类型,使用mget批量获取 。 -
第二: 分页 获取 文章列表 时,
lrange命令在列表 两端性能较好 ,但是如果 列表较大 ,获取列表 中间范围 的元素 性能会变差 。此时可以考虑将列表做 二级拆分 ,或者使用Redis 3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表 中间范围 的元素时也可以 高效完成 。
3.3. 其他场景
实际上列表的使用场景很多,具体可以参考如下:
| 命令组合 | 对应数据结构 |
|---|---|
| lpush + lpop | Stack(栈) |
| lpush + rpop | Queue(队列) |
| lpush + ltrim | Capped Collection(有限集合) |
| lpush + brpop | Message Queue(消息队列) |
小结
本文介绍了 Redis 中的 列表 的 一些 基本命令 、 内部编码 和 适用场景 。通过组合不同 命令 ,可以把 列表 转换为不同的 数据结构 使用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入剖析Redis系列(六) - Redis数据结构之哈希
- 深入剖析Redis系列(八) - Redis数据结构之集合
- 深入剖析Redis系列(四) - Redis数据结构与全局命令概述
- 深入剖析Redis系列(五) - Redis数据结构之字符串
- kafka集群Producer基本数据结构及工作流程深入剖析-kafka 商业环境实战
- 【Java集合源码剖析】ArrayList源码剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms on Strings, Trees and Sequences
Dan Gusfield / Cambridge University Press / 1997-5-28 / USD 99.99
String algorithms are a traditional area of study in computer science. In recent years their importance has grown dramatically with the huge increase of electronically stored text and of molecular seq......一起来看看 《Algorithms on Strings, Trees and Sequences》 这本书的介绍吧!