《Redis入门指南(第2版)》一3.6 有序集合类型

3.6 有序集合类型

了解了集合类型后,小白终于被Redis的强大功能所折服了,但他却不愿止步于此。这不,小白又想给博客加上按照文章访问量排序的功能:

老师您好,之前您已经介绍过了如何使用列表类型键存储文章ID列表,不过我还想加上按照文章访问量排序的功能,因为我觉得很多访客更希望看那些热门的文章。

宋老师回答到:

这个功能很好实现,不过要用到一个新的数据类型,也是我要介绍的最后一个数据类型—有序集合。

3.6.1 介绍

有序集合类型(sorted set)的特点从它的名字中就可以猜到,它与上一节介绍的集合类型的区别就是“有序”二字。

在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。

有序集合类型在某些方面和列表类型有些相似。

(1)二者都是有序的。

(2)二者都可以获得某一范围的元素。

但是二者有着很大的区别,这使得它们的应用场景也是不同的。

(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。

(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。

(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。

(4)有序集合要比列表类型更耗费内存。

有序集合类型算得上是Redis的5种数据类型中最高级的类型了,在学习时可以与列表类型和集合类型对照理解。

3.6.2 命令

1.增加元素

ZADD key score member [score member …]

ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。

假设我们用有序集合模拟计分板,现在要记录Tom、Peter和David三名运动员的分数(分别是89分、67分和100分):

redis>ZADD scoreboard 89 Tom 67 Peter 100 David
(integer) 3

这时我们发现Peter的分数录入有误,实际的分数应该是76分,可以用ZADD命令修改Peter的分数:

redis>ZADD scoreboard 76 Peter
(integer) 0

分数不仅可以是整数,还支持双精度浮点数:

redis>ZADD testboard 17E+307 a
(integer) 1
redis>ZADD testboard 1.5 b
(integer) 1
redis>ZADD testboard +inf c
(integer) 1
redis>ZADD testboard -inf d
(integer) 1

其中+inf和-inf分别表示正无穷和负无穷。

2.获得元素的分数

ZSCORE key member

示例如下:

redis>ZSCORE scoreboard Tom
"89"

3.获得排名在某个范围的元素列表

ZRANGE key start stop [WITHSCORES]

ZREVRANGE key start stop [WITHSCORES]

ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。ZRANGE命令与LRANGE命令十分相似,如索引都是从0开始,负数代表从后向前查找(−1表示最后一个元素)。就像这样:

redis>ZRANGE scoreboard 0 2
1) "Peter"
2) "Tom"
3) "David"
redis>ZRANGE scoreboard 1 -1
1) "Tom"
2) "David"

如果需要同时获得元素的分数的话可以在ZRANGE命令的尾部加上WITHSCORES参数,这时返回的数据格式就从“元素1, 元素2, …, 元素n”变为了“元素1, 分数1, 元素2, 分数2, …, 元素n, 分数n”,例如:

redis>ZRANGE scoreboard 0 -1 WITHSCORES
1) "Peter"
2) "76"
3) "Tom"
4) "89"
5) "David"
6) "100"

ZRANGE命令的时间复杂度为O(log n+m)(其中n为有序集合的基数,m为返回的元素个数)。

如果两个元素的分数相同,Redis会按照字典顺序(即"0" < "9" < "A" < "Z" < "a" < "z"这样的顺序)来进行排列。再进一步,如果元素的值是中文怎么处理呢?答案是取决于中文的编码方式,如使用UTF-8编码:

redis>ZADD chineseName 0 马华 0 刘墉 0 司马光 0 赵哲
(integer) 4
redis>ZRANGE chineseName 0 -1
1) "\xe5\x88\x98\xe5\xa2\x89"
2) "\xe5\x8f\xb8\xe9\xa9\xac\xe5\x85\x89"
3) "\xe8\xb5\xb5\xe5\x93\xb2"
4) "\xe9\xa9\xac\xe5\x8d\x8e"

可见此时Redis依然按照字典顺序排列这些元素。

ZREVRANGE命令和ZRANGE的唯一不同在于ZREVRANGE命令是按照元素分数从大到小的顺序给出结果的。

4.获得指定分数范围的元素

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

ZRANGEBYSCORE命令参数虽然多,但是都很好理解。该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)的元素:

redis>ZRANGEBYSCORE scoreboard 80 100
1) "Tom"
2) "David"

如果希望分数范围不包含端点值,可以在分数前加上“(”符号。例如,希望返回”80分到100分的数据,可以含80分,但不包含100分,则稍微修改一下上面的命令即可:

redis>ZRANGEBYSCORE scoreboard 80 (100
1) "Tom"

min和max还支持无穷大,同ZADD命令一样,-inf和+inf分别表示负无穷和正无穷。比如你希望得到所有分数高于80分(不包含80分)的人的名单,但你却不知道最高分是多少(虽然有些背离现实,但是为了叙述方便,这里假设可以获得的分数是无上限的),这时就可以用上+inf了:

redis>ZRANGEBYSCORE scoreboard (80 +inf
1) "Tom"
2) "David"

WITHSCORES参数的用法与ZRANGE命令一样,不再赘述。

了解SQL语句的读者对LIMIT offset count应该很熟悉,在本命令中LIMIT offset count 与 SQL 中的用法基本相同,即在获得的元素列表的基础上向后偏移offset个元素,并且只获取前count个元素。为了便于演示,我们先向scoreboard键中再增加些元素:

redis>ZADD scoreboard 56 Jerry 92 Wendy 67 Yvonne
(integer) 3

现在scoreboard键中的所有元素为:

redis>ZRANGE scoreboard 0 -1 WITHSCORES
1) "Jerry"
2) "56"
3) "Yvonne"
4) "67"
5) "Peter"
6) "76"
7) "Tom"
8) "89"
9) "Wendy"
10) "92"
11) "David"
12) "100"

想获得分数高于60分的从第二个人开始的3个人:

redis>ZRANGEBYSCORE scoreboard 60 +inf LIMIT 1 3
1) "Peter"
2) "Tom"
3) "Wendy"

那么,如果想获取分数低于或等于 100 分的前 3 个人怎么办呢?这时可以借助ZREVRANGEBYSCORE命令实现。对照前文提到的ZRANGE命令和ZREVRANGE命令之间的关系,相信读者很容易能明白 ZREVRANGEBYSCORE 命令的功能。需要注意的是ZREVRANGEBYSCORE 命令不仅是按照元素分数从大往小的顺序给出结果的,而且它的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。就像这样:

redis>ZREVRANGEBYSCORE scoreboard 100 0 LIMIT 0 3
1) "David"
2) "Wendy"
3) "Tom"

5.增加某个元素的分数

ZINCRBY key increment member

ZINCRBY 命令可以增加一个元素的分数,返回值是更改后的分数。例如,想给Jerry加4分:

redis>ZINCRBY scoreboard 4 Jerry
"60"
increment也可以是个负数表示减分,例如,给Jerry减4分:

redis>ZINCRBY scoreboard -4 Jerry
"56"

如果指定的元素不存在,Redis 在执行命令前会先建立它并将它的分数赋为 0 再执行操作。

3.6.3 实践

1.实现按点击量排序
要按照文章的点击量排序,就必须再额外使用一个有序集合类型的键来实现。在这个键中以文章的 ID 作为元素,以该文章的点击量作为该元素的分数。将该键命名为posts:page.view,每次用户访问一篇文章时,博客程序就通过ZINCRBY posts:page. view 1文章ID更新访问量。

需要按照点击量的顺序显示文章列表时,有序集合的用法与列表的用法大同小异:

$postsPerPage = 10
$start = ($currentPage - 1) * $postsPerPage
$end = $currentPage * $postsPerPage - 1
$postsID =ZREVRANGE posts:page.view, $start,$end
for each $id in $postsID
 $postData =HGETALL post:$id
 print 文章标题:$postData.title

另外3.2节介绍过使用字符串类型键post:文章ID:page.view来记录单个文章的访问量,现在这个键已经不需要了,想要获得某篇文章的访问量可以通过ZSCORE posts:page. view文章ID来实现。

2.改进按时间排序
3.4节介绍了每次发布新文章时都将文章的ID加入到名为posts:list的列表类型键中来获得按照时间顺序排列的文章列表,但是由于列表类型更改元素的顺序比较麻烦,而如今不少博客系统都支持更改文章的发布时间,为了让小白的博客同样支持该功能,我们需要一个新的方案来实现按照时间顺序排列文章的功能。

为了能够自由地更改文章发布时间,可以采用有序集合类型代替列表类型。自然地,元素仍然是文章的ID,而此时元素的分数则是文章发布的Unix时间14。通过修改元素对应的分数就可以达到更改时间的目的。

14 Unix时间指UTC时间1970年1月1日0时0分0秒起至现在的总秒数(不包括闰秒)。为什么是1970年呢?因为Unix在1970年左右诞生。
另外借助ZREVRANGEBYSCORE命令还可以轻松获得指定时间范围的文章列表,借助这个功能可以实现类似WordPress的按月份查看文章的功能。

3.6.4 命令拾遗

1.获得集合中元素的数量
ZCARD key
例如:

redis>ZCARD scoreboard
(integer) 6

2.获得指定分数范围内的元素个数

ZCOUNT key min max

例如:

redis>ZCOUNT scoreboard 90 100
(integer) 2

ZCOUNT命令的min和max参数的特性与ZRANGEBYSCORE命令中的一样:

redis>ZCOUNT scoreboard (89 +inf
(integer) 2

3.删除一个或多个元素

ZREM key member [member …]

ZREM命令的返回值是成功删除的元素数量(不包含本来就不存在的元素)。

redis>ZREM scoreboard Wendy
(integer) 1
redis>ZCARD scoreboard
(integer) 5

4.按照排名范围删除元素

ZREMRANGEBYRANK key start stop

ZREMRANGEBYRANK命令按照元素分数从小到大的顺序(即索引0表示最小的值)删除处在指定排名范围内的所有元素,并返回删除的元素数量。如:

redis>ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 6
redis>ZREMRANGEBYRANK testRem 0 2
(integer) 3
redis>ZRANGE testRem 0 -1
1) "d"
2) "e"
3) "f"

5.按照分数范围删除元素

ZREMRANGEBYSCORE key min max

ZREMRANGEBYSCORE命令会删除指定分数范围内的所有元素,参数min和max的特性和ZRANGEBYSCORE命令中的一样。返回值是删除的元素数量。如:

redis>ZREMRANGEBYSCORE testRem (4 5
(integer) 1
redis>ZRANGE testRem 0 -1
1) "d"
2) "f"
6.获得元素的排名
ZRANK key member

ZREVRANK key member

ZRANK命令会按照元素分数从小到大的顺序获得指定的元素的排名(从0开始,即分数最小的元素排名为0)。如:

redis>ZRANK scoreboard Peter
(integer) 0

ZREVRANK命令则相反(分数最大的元素排名为0):

redis>ZREVRANK scoreboard Peter
(integer) 4

7.计算有序集合的交集

ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]]
 [AGGREGATE SUM|MIN|MAX]

ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination键中(同样以有序集合类型存储),返回值为destination键中的元素个数。

destination键中元素的分数是由AGGREGATE参数决定的。

(1)当AGGREGATE是SUM时(也就是默认值),destination键中元素的分数是每个参与计算的集合中该元素分数的和。例如:

redis> ZADD sortedSets1 1 a 2 b
(integer) 2
redis> ZADD sortedSets2 10 a 20 b
(integer) 2
redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2
(integer) 2
redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "11"
3) "b"
4) "22"

(2)当AGGREGATE是MIN时,destination键中元素的分数是每个参与计算的集合中该元素分数的最小值。例如:

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MIN
(integer) 2
redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"

(3)当AGGREGATE是MAX时,destination键中元素的分数是每个参与计算的集合中该元素分数的最大值。例如:

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MAX
(integer) 2
redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "10"
3) "b"
4) "20"

ZINTERSTORE命令还能够通过WEIGHTS参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该集合的权重。例如:

ZINTERSTORE命令还能够通过WEIGHTS参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该集合的权重。例如:

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 WEIGHTS 1 0.1
(integer) 2
redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "2"
3) "b"
4) "4"

另外还有一个命令与ZINTERSTORE命令的用法一样,名为ZUNIONSTORE,它的作用是计算集合间的并集,这里不再赘述。

时间: 2024-10-26 14:27:38

《Redis入门指南(第2版)》一3.6 有序集合类型的相关文章

《Redis入门指南(第2版)》一第3章 入门

第3章 入门 Redis入门指南(第2版) 学会如何安装和运行Redis,并了解Redis的基础知识后,本章将详细介绍Redis的5种主要数据类型及相应的命令,带领读者真正进入Redis的世界.在学习的时候,手边打开一个redis-cli程序来跟着一起输入命令将会极大地提高学习效率.尽管在目前多数公司和团队的Redis的应用是以缓存和队列为主. 在之后的章节中你会遇到两个学习伙伴:小白和宋老师.小白是一个标准的极客,最近刚开始他的Redis学习之旅,而他大学时的计算机老师宋老师恰好对Redis颇

《Redis入门指南(第2版)》一第2章 准备

第2章 准备 Redis入门指南(第2版)"纸上得来终觉浅,绝知此事要躬行." --陆游<冬夜读书示子聿> 学习Redis最好的办法就是动手尝试它.在介绍Redis最核心的内容之前,本章先来介绍一下如何安装和运行Redis,以及Redis的基础知识,使读者可以在之后的章节中一边学习一边实践.

《Redis入门指南(第2版)》一导读

前 言 Redis入门指南(第2版)Redis如今已经成为Web开发社区中最火热的内存数据库之一,而它的诞生距现在不过才4年.随着Web 2.0的蓬勃发展,网站数据快速增长,对高性能读写的需求也越来越多,再加上半结构化的数据比重逐渐变大,人们对早已被铺天盖地地运用着的关系数据库能否适应现今的存储需求产生了疑问.而Redis的迅猛发展,为这个领域注入了全新的思维. Redis凭借其全面的功能得到越来越多的公司的青睐,从初创企业到新浪微博这样拥有着几百台Redis服务器的大公司,都能看到Redis的

《Redis入门指南(第2版)》一3.1 热身

3.1 热身 在介绍Redis的数据类型之前,我们先来了解几个比较基础的命令作为热身,赶快打开redis-cli,跟着样例亲自输入命令来体验一下吧! 1.获得符合规则的键名列表 KEYS pattern pattern支持glob风格通配符格式,具体规则如表3-1所示. 现在Redis中空空如也(如果你从第2章开始就一直跟着本书的进度输入命令,此时数据库中可能还会有个foo键),为了演示KEYS命令,首先我们得给Redis加点料.使用SET命令(会在3.2节介绍)建立一个名为bar的键: red

《Redis入门指南(第2版)》一3.4 列表类型

3.4 列表类型 正当小白踌躇满志地写着文章列表页的代码时,一个很重要的问题阻碍了他的开发,于是他请来了宋老师为他讲解. 原来小白是使用如下流程获得文章列表的: 读取posts:count键获得博客中最大的文章ID:根据这个ID来计算当前列表页面中需要展示的文章ID列表(小白规定博客每页只显示10篇文章,按照ID的倒序排列),如第n页的文章ID范围是从最大的文章ID - (n - 1) 10"到"max(最大的文章ID - n 10 + 1, 1)":对每个ID使用HMGET

《Redis入门指南》一4.3 排序

4.3 排序 Redis入门指南 午后,宋老师正在批改学生们提交的程序,再过几天就会迎来第一次计算机全市联考.他在每个学生的程序代码末尾都用注释详细地做了批注--严谨的治学态度让他备受学生们的爱戴. 一个电话打来."小白的?"宋老师拿出手机,"博客最近怎么样了?"未及小白开口,他就抢先问道. 特别好!现在平均每天都有50多人访问我的博客.不过昨天我收到一个访客的邮件,他向我反映了一个问题:查看一个标签下的文章列表时文章不是按照时间顺序排列的,找起来很麻烦.我看了一下

《Redis入门指南》一5.1 PHP与Redis

5.1 PHP与Redis Redis入门指南 Redis官方推荐的PHP客户端是Predis1和phpredis2.前者是完全使用PHP代码实现的原生客户端,而后者则是使用C语言编写的PHP扩展.在功能上两者区别并不大,就性能而言后者会更胜一筹.考虑到很多主机并未提供安装PHP扩展的权限,本节会以Predis为示例介绍如何在PHP中使用Redis. 虽然Predis的性能逊于phpredis,但是除非执行大量Redis命令,否则很难区分二者的性能.而且实际应用中执行Redis命令的开销更多在网

《Redis入门指南》一5.3 Python与Redis

5.3 Python与Redis Redis入门指南 Redis官方推荐的Python客户端是redis-py1. 5.3.1 安装 推荐使用pip install redis安装最新版本的redis-py,也可以使用easy_install:easy_install redis. 5.3.2 使用方法 首先需要引入redis-py: import redis 下面的代码将创建一个默认连接到地址127.0.0.1,端口6379的Redis连接: r = redis.StrictRedis() 也

《Redis入门指南》一4.1 事务

4.1 事务 Redis入门指南 傍晚时候,忙完了一天的教学工作,宋老师坐在办公室的电脑前开始为明天的课程做准备.尽管有着近5年的教学经验,可是宋老师依然习惯在备课时写一份简单的教案.正在网上查找资料时,在浏览器的历史记录里他突然看到了小白的博客.心想:不知道他的博客怎么样了? 于是宋老师点进了小白的博客,页面刚载入完他就被博客最下面的一行大得夸张的文字吸引了:"Powered by Redis".宋老师笑了笑,接着就看到了小白博客中最新的一篇文章: 标题: 使用Redis来存储微博中