Redis学习笔记(3) Redis基础类型及命令之二
1. 集合类型
集合类型与列表类型有很多相似之处,但二者的区别在于:前者具有唯一性,但不具有有序性;后者具有有序性,但不具有唯一性。集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,以及多个集合类型之间进行并集、交集和差集运算。
(1) 命令
1) 增加/删除元素
格式为:SADD key member [member ...] SREM key member [member ...]
SADD向集合中增加一个或多个元素,加入的元素若已存在语集合中,则会忽略该元素。命令返回成功加入的元素数量(忽略不计算在内)。SREM命令用于从集合中删除一个或多个元素,并返回删除成功的个数。
127.0.0.1:6379[2]> SADD letters a (integer) 1 127.0.0.1:6379[2]> SADD letters a b c (integer) 2 127.0.0.1:6379[2]> TYPE letters set # 由于"d"在集合中不存在,所以只删除了一个元素,返回值为1 127.0.0.1:6379[2]> SREM letters c d (integer) 1
2) 获得集合中所有元素
格式为:SMEMBERS key
127.0.0.1:6379[2]> SMEMBERS letters 1) "b" 2) "a"
3) 判断元素是否在集合中
格式为:SISMEMBER key member,该命令的时间复杂度为O(1),值存在时返回1,否则返回0。
127.0.0.1:6379[2]> SISMEMBER letters a (integer) 1 127.0.0.1:6379[2]> SISMEMBER letters d (integer) 0
4) 集合间运算
格式为:SDIFF key (key ...) SINTER key (key ...) SUNION key (key ...)
SDIFF是对多个集合执行差集运算,如{1,2,3} - {2,3,4} = {1},{2,3,4} - {1,2,3} = {4}
127.0.0.1:6379[2]> SADD set1 1 2 3 (integer) 3 127.0.0.1:6379[2]> SADD set2 2 3 4 (integer) 3 127.0.0.1:6379[2]> SDIFF set1 set2 1) "1" 127.0.0.1:6379[2]> SDIFF set2 set1 1) "4" # SDIFF支持同时传入多个键,顺序为先计算set1-set2,再计算结果与set3的差集 127.0.0.1:6379[2]> sadd set3 2 3 4 5 (integer) 4 127.0.0.1:6379[2]> sdiff set1 set2 set3 1) "1"
SINTER命令用来对多个集合执行交集运算,如{1,2,3} & {2,3,4} = {2,3}
127.0.0.1:6379[2]> SDIFF set1 set2 set3 1) "1" 127.0.0.1:6379[2]> SINTER set1 set2 set3 1) "2" 2) "3"
SUNION用来对多个集合执行并集运算,如{1,2,3} or {2,3,4} = {1,2,3,4}.
127.0.0.1:6379[2]> SUNION set1 set2 set3 1) "1" 2) "2" 3) "3" 4) "4"
5) 获取集合中元素的个数
格式为:SCARD key
127.0.0.1:6379[2]> SMEMBERS set2 1) "2" 2) "3" 3) "4" 127.0.0.1:6379[2]> SCARD set2 (integer) 3
6) 进行集合运算并将结果存储
格式为:SDIFFSTORE destionation key [key...] SINTERSTORE destination key [key ...] SUNIONSTORE destination key [key ...]
127.0.0.1:6379[2]> SDIFFSTORE result set1 set2 (integer) 1 127.0.0.1:6379[2]> get reult (nil) 127.0.0.1:6379[2]> SMEMBERS result 1) "1" 127.0.0.1:6379[2]> TYPE result set
7) 随机获得集合中的元素
格式为:SRANDMEMBER key [count],count表示一次获取多个元素,当count为正数,SRANDMEMBER会随机从集合中获得count个不重复的元素,如果count的值大于集合中的元素个数,SRANDMEMBER会返回集合中的全部元素;如果count为负数,SRANDMEMBER会随机从集合中获得|count|个元素,且这些元素有可能相同。
127.0.0.1:6379[2]> SRANDMEMBER set1 "2" 127.0.0.1:6379[2]> SRANDMEMBER set1 "1" 127.0.0.1:6379[2]> 127.0.0.1:6379[2]> SADD letters a b c d (integer) 4 127.0.0.1:6379[2]> SRANDMEMBER letters 2 1) "d" 2) "a" 127.0.0.1:6379[2]> SRANDMEMBER letters 100 1) "d" 2) "a" 3) "b" 4) "c" 127.0.0.1:6379[2]> SRANDMEMBER letters -2 1) "d" 2) "d" 127.0.0.1:6379[2]> SRANDMEMBER letters -10 1) "a" 2) "a" 3) "a" 4) "d" 5) "d" 6) "d" 7) "c" 8) "b" 9) "d" 10) "d"
8) 从集合中弹出一个元素
格式为:SPOP key,LPOP是从列表左边弹出一个元素,但集合类型的元素是无序的,SPOP会从集合中随机选择一个元素弹出。
127.0.0.1:6379[2]> SPOP letters "d" 127.0.0.1:6379[2]> SMEMBERS letters 1) "c" 2) "b" 3) "a"
(2) 示例
1) 存储文章标签:一个文章的所有标签互补相同,且对标签排序不要求,对每篇文章使用键为"post:文章ID:tags"的键存储文章标签,伪代码:
# ID为42的文章添加标签 SADD post:42:tags 闲言碎语,技术文章, Java # 删除标签 SREM post:42:tags, 闲言碎语 # 显示所有标签 $tags = SMEMBERS post:42:tags print $tags
注意:使用集合类型键存储标签适合需要单独增加或删除标签的场合。其次,有些地方需要用户直接设置所有标签后一起上传。
2) 通过标签搜索文章
列出某个标签下的所有文章,甚至获得同时属于几个标签的文章列表,为每个标签使用一个"tags:标签名称:posts"的集合类型存储标有该标签的文章ID列表。当需要获取"MySQL"标签的文章时只需要使用命令SMEMBERS tags:MYSQL:posts;当同时找出属于Java、MYSQL、Redis三个标签的文章,只需要将tag:java:posts、tags:MYSQL:posts和tags:Redis:posts这3个键取交集,借助SINTER命令。
2. 有序集合类型
有序集合类型为集合中的每一个元素都关联了一个分数,不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作,并且元素的分数可以相同。
有序集合与列表的区别:
1) 列表通过链表实现,获取靠近两端的数据速度极快,二访问中间数据的速度会较慢,适合实现"新鲜事"或"日志"这样很少访问中间元素的应用。
2) 有序集合是使用散列表和跳跃表(skip list)实现的,所以即使读取位于中间部分的数据速度也很快。
3) 列表中不能简单地调整某个元素的位置,但有序集合可以(通过改变元素的分数)。
4) 有序集合要比列表类型更耗费内存。
(1) 命令
1) 增加元素
格式为:ZADD key score member [score member ...]
ZADD用于向有序集合加入一个元素和该元素的分数,若已存在,则会使用新的分数替换原有的分数,返回新加入集合中的元素个数。
# 记录三个人的分数 127.0.0.1:6379> ZADD scoreboard 89 Tom 67 Peter 100 David (integer) 3 # 修改Peter的分数 127.0.0.1:6379> ZADD scoreboard 76 Peter (integer) 0 # 分数不仅可以是整数,还支持double类型 127.0.0.1:6379> ZADD testboard 17E+307 a (integer) 1 127.0.0.1:6379> ZADD testboard 1.5 b (integer) 1 127.0.0.1:6379> ZADD testboard +inf c #正无穷 (integer) 1 127.0.0.1:6379> ZADD testboard -inf d #负无穷 (integer) 1
2) 获得元素的分数
格式为: ZSCORE key member
127.0.0.1:6379> ZSCORE scoreboard Peter "76"
3) 获得排名在某个范围的元素列表
格式为:ZRANGE key start stop [WITHSCORES] ZREVRANGE key start stop [WITHSCORE]
ZRANGE会按元素分数从小到大顺序返回索引从start到stop之间的所有元素(包含两端的元素),同时获得元素的分数可以加上WITHSCORES参数。ZRANGE索引从0开始,负数代表从后向前查找(-1表示最后一个元素);ZREVRANG会按照元素分数从大到小的顺序给出结果。
127.0.0.1:6379> ZRANGE scoreboard 0 2 1) "Peter" 2) "Tom" 3) "David" 127.0.0.1:6379> ZRANGE scoreboard 1 -1 1) "Tom" 2) "David" 127.0.0.1:6379> ZRANGE scoreboard 0 -1 WITHSCORES 1) "Peter" 2) "76" 3) "Tom" 4) "89" 5) "David" 6) "100"
如果两个元素的分数相同,Redis会按照字典顺序(0<9<A<Z<a<z)来进行排序,再进一步,如果元素的值是中文,则取决于中文的编码方式。
# 分数相同情况 127.0.0.1:6379> ZRANGE scoreboard 0 -1 WITHSCORES 1) "Peter" 2) "76" 3) "Tom" 4) "89" 5) "David" 6) "100" 7) "Oliver" 8) "100" # 中文情况 127.0.0.1:6379> ZADD chineseName 0 马华 0 黎明 0 姚明 0 李娜 (integer) 4 127.0.0.1:6379> ZRANGE chineseName 0 -1 WITHSCORES 1) "\xe5\xa7\x9a\xe6\x98\x8e" 2) "0" 3) "\xe6\x9d\x8e\xe5\xa8\x9c" 4) "0" 5) "\xe9\xa9\xac\xe5\x8d\x8e" 6) "0" 7) "\xe9\xbb\x8e\xe6\x98\x8e" 8) "0"
4) 获得指定分数范围的元素
格式为:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
该命令按照元素大从小到大返回分数在min和max之间的元素。如果希望分数不包含端点值,可以在分数前加上"("符号,如获取80到100分的数据,包含80,不包含100。
LIMIT offset count时在获得的元素列表的基础上向后偏移offset个元素,并且只获取前count个元素。
# 获取分数在[80,100]间的元素 127.0.0.1:6379> ZRANGEBYSCORE scoreboard 80 100 1) "Tom" 2) "David" 3) "Oliver" # 获取分数在[80,100)之间的元素,包含80,不包含100的元素 127.0.0.1:6379> ZRANGEBYSCORE scoreboard 80 (100 1) "Tom" # 获取分数在(80,+inf]之间的元素,即大于80的元素 127.0.0.1:6379> ZRANGEBYSCORE scoreboard (80 +inf 1) "Tom" 2) "David" 3) "Oliver" # scoreboard的元素以及分数 127.0.0.1:6379> ZRANGE scoreboard 0 -1 WITHSCORES 1) "Peter" 2) "76" 3) "Tom" 4) "89" 5) "David" 6) "100" 7) "Oliver" 8) "100" # 获取分数高于80分的从第二个人开始的2个人 127.0.0.1:6379> ZRANGEBYSCORE scoreboard 80 +inf WITHSCORES LIMIT 1 2 1) "David" 2) "100" 3) "Oliver" 4) "100" # 获取分数低于100的前2个人 127.0.0.1:6379> ZREVRANGEBYSCORE scoreboard (100 0 WITHSCORES LIMIT 0 2 1) "Tom" 2) "89" 3) "Peter" 4) "76"
5) 增加某个元素的分数
格式为:ZINCRBY key increment member
ZINCRBY可以增加一个元素的分数(正数和负数均可),返回值是更改后的分数。如果指定元素不存在,Redis会先建立它并将它的分数赋为0再执行操作。
127.0.0.1:6379> ZRANGE scoreboard 0 -1 WITHSCORES 1) "Peter" 2) "76" 3) "Tom" 4) "89" 5) "David" 6) "100" 7) "Oliver" 8) "100" # 将Tom的分数减少5分 127.0.0.1:6379> ZINCRBY scoreboard -5 Tom "84"
6) 获得集合中的元素数量
格式为:ZCRAD key
127.0.0.1:6379> ZCARD scoreboard (integer) 4
7) 获得指定分数范围的元素个数
格式为:ZCOUNT key min max
127.0.0.1:6379> ZCOUNT scoreboard (80 +inf (integer) 3
8) 删除一个或多个元素
ZREM key member [member ...]
127.0.0.1:6379> ZREM scoreboard Oliver (integer) 1 127.0.0.1:6379> ZCARD scoreboard (integer) 3
9) 按照排名范围删除元素
ZREMRANGEBYRANK key start stop
该命令按照元素分数从大到小的顺序删除指定排名范围内的所有元素,并返回删除的元素数量。
127.0.0.1:6379> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f (integer) 6 127.0.0.1:6379> ZREMRANGEBYRANK testRem 0 2 (integer) 3 127.0.0.1:6379> ZRANGE testRem 0 -1 WITHSCORES 1) "d" 2) "4" 3) "e" 4) "5" 5) "f" 6) "6"
10) 按照分数范围删除元素
格式为:ZREMRANGEBYSCORE key min max
该命令会删除指定分数范围内的所有元素,返回值是删除的元素数量。
127.0.0.1:6379> ZREMRANGEBYSCORE testRem (4 5 (integer) 1 127.0.0.1:6379> ZRANGE testRem 0 -1 WITHSCORES 1) "d" 2) "4" 3) "f" 4) "6"
11) 获得元素的排名
格式为:ZRANK key member ZREVRANK key member
ZRANK会按照分数从小到大的顺序获得指定的元素排名。ZREVRANK命令则相反。
# 从小到大的顺序获取Peter的排名 127.0.0.1:6379> ZADD scoreboard 70 Peter 80 Tom 90 Green 100 William (integer) 4 127.0.0.1:6379> ZRANK scoreboard Peter (integer) 0 # 从大到小的顺序获取其排序 127.0.0.1:6379> ZREVRANK scoreboard Peter (integer) 3
12) 计算有序集合的交集
格式为:ZINTERSTORE destination numkeys key [key ...] [WEIGHT weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
该命令用来计算多个有序集合的交集并将结果存储在destination中,返回值为destination的个数,destination键中元素的分数是由AGGREGATE参数决定的。
a. 当AGGREGATE是SUM时(默认),destination键中元素的分数是每个参与计算的集合中该元素的分数的和。
127.0.0.1:6379> ZADD sortedSets1 1 a 2 b (integer) 2 127.0.0.1:6379> ZADD sortedSets2 10 a 20 b (integer) 2 127.0.0.1:6379> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 (integer) 2 127.0.0.1:6379> ZRANGE sortedSetsResult 0 -1 WITHSCORES 1) "a" 2) "11" 3) "b" 4) "22"
b. 当AGGREGATE是MIN时。destination键中元素的分数是每个参与计算的集合中该元素分数的最小值。
127.0.0.1:6379> ZINTERSTORE sortedSetResult 2 sortedSets1 sortedSets2 AGGREGATE MIN (integer) 2 127.0.0.1:6379> ZRANGE sortedSetResult 0 -1 WITHSCORES 1) "a" 2) "1" 3) "b" 4) "2"
c. 当AGGREGATE是MAX时,destination键中元素的分数是每个参与计算的集合中该元素的最大值。
127.0.0.1:6379> ZINTERSTORE sortedSetResult 2 sortedSets1 sortedSets2 AGGREGATE MAX (integer) 2 127.0.0.1:6379> ZRANGE sortedSetResult 0 -1 WITHSCORES 1) "a" 2) "10" 3) "b" 4) "20"
ZINTERSTORE命令还能够通过WEIGHTS参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该集合的权重。
127.0.0.1:6379> ZINTERSTORE sortedSetResult 2 sortedSets1 sortedSets2 WEIGHTS 1 0.1 (integer) 2 127.0.0.1:6379> ZRANGE sortedSetResult 0 -1 WITHSCORES 1) "a" 2) "2" 3) "b" 4) "4"
13) 计算集合间的并集
与ZINTERSTORE命令用法一样
(2) 示例
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
获得某篇文章的访问量可通过:ZSCORE posts:page.view 文章ID来实现。
2) 改进按时间排序
为了能自由地更改文章发布时间,可采用有序集合类型替代列表类型,元素仍为文章ID,此时元素的分数是文章发布的LInux时间,可以修改元素对应的分数就可以达到更改时间的目的。
借助ZREVRANGEBYSCORE命令还可以轻松获得指定时间范围的文章列表,借助这个功能可以实现类似WordPress的按月份查看文章的功能。