Redis-07Redis数据结构--有序集合ZSet
文章目錄
- 概述
- 有序集合的數據結構
- Redis 有序集合的部分命令
- spring-data-redis 對有序集合的封裝
- 使用 Spring 操作有序集合
- 注意
- 代碼
概述
有序集合和集合類似,只是說它是有序的,和無序集合的主要區別在于每一個元素除了值之外,它還會多一個分數。
有序集合的數據結構
有序集合是依賴 key 標示它是屬于哪個集合,依賴分數進行排序,所以值和分數是必須的,而實際上不僅可以對分數進行排序,在滿足一定的條件下,也可以對值進行排序 。
Redis 有序集合的部分命令
官網: https://redis.io/commands#sorted_set
有序集合和無序集合的命令是接近的,只是在這些命令的基礎上,會增加對于排序的操作,這些是我們在使用的時候需要注意的細節.
有些時候 Redis 借助數據區間的表示方法來表示包含或者不包含,比如在數學的區間表示中[2,5 ]表示包含 2,但是不包含 5 的 區間。
| zadd key score1 value1 [score2 value2 …] | 向有序集合的 key,增加一個或者多個成員 | 如果不存在對應的 key,則創建鍵為 key 的有序集合 |
| zcard key | 獲取有序集合的成員數 | ----- |
| zcount key min max | 根據分數返回對應的成員列表 | min 為最小值, max為最大值,默認為包含min 和 max 值,采用數學區間表示的方法,如果需要不包含,則在分數前面加入“(”,注意不支持“[”表示 |
| zincrby key increment member | 給有序集合成員值為 member 的分數增加 increment | ----- |
| zinterstore desKey nurnkeys key1 [key2 key3 …] | 求多個有序集合的交集,并且將結果保存到 des Key 中 | numkeys 是一個整數,表示多少個有序集合 |
| zlexcount key min max | 求有序集合 key 成員值在 min 和 max 的范圍 | 這里范圍為 key 的成員值, Redis 借助數據區間的表示方法,“[”表示包含該值,“(”表示不包含該值 |
| zrange key start stop [withscores] | 按照分值的大小〈從小到大)返回成員,加入 start 和 stop 參數可以截取某一段返回.如果輸入可選項 withscores,則連同分數一起返回 | 這里記集合最大長度為len,Redis 會將集合排序后,形成一個從 0 到len-1的下標,然后根據 start 和 stop 控制的下標(包含 start 和 stop)返回 |
| zrank key member | 按從小到大求有序集合的排行 | 排名第一的為 0,第二的為 1 … |
| zrangebylex key min max [limit offset count] | 根據值的大小,從小到大排序, min 為最小值, max 為最大值;limit 選項可選,當 Red is 求出范圍集合后,會生產下標0到n,然后根據偏移量offset 和限定返回 數 count,返回對應的成員 | 這里范圍為 key 的成員值, Red i s 借助數學區間的表示方法,“[”表示包含該值,“(”表示不包含該值 |
| zrangebyscore key min max [withscores] [limit offset count] | 根據分數大小,從小到大求取范圍,選項 withscores 和 limit 請參考 zrange 命令和zrangebylex 說明 | 根據分析求取集合的范圍。這里默認包含 min和 max,如果不想包含,則在參數前加入“(”,注意不支持“ [”表示 |
| zremrangebyscore key start stop | 根據分數區間進行刪除 | 按照 socre 進行排序,然后排除 0 到len-1的下標,然后根據 start 和 stop 進行刪除, Redis 借助數學區間的表示方法,“[”表示包含該值,“(”表示不包含該值 |
| zremrangebyrank key start stop | 按照分數排行從小到大的排序刪除,從0開始計算 | ----- |
| zremrangebylex key min max | 按照值的分布進行刪除 | ----- |
| zrevrange key start stop [withscores] | 從大到小的按分數排序,參數請參見zrange | 與 zrange 相同,只是排序是從大到小 |
| zrevrangebyscore key max min [withscores] | 從大到小的按分數排序,參數請參見zrangebyscore | 與 zrangebyscore 相同 ,只是排序是從大到小 |
| zrevrank key member | 按從大到小的順序,求元素的排行 | 排名第一位 0,第二位1 … |
| zscore key member | 返回成員的分數值 | 返回成員的分數 |
| zunionstore desKey numKeys key1 [key2 key3 key4 …] | 求多個有序集合的并集,其中 numKeys是有序,集合的個數 | ----- |
在對有序集合、下標、區間的表示方法進行操作的時候,需要十分小心命令,注意它是操作分數還是值,稍有不慎就會出現問題。
# 為了測試的數據干凈,刪除當前db的數據 127.0.0.1:6379> FLUSHDB OK 127.0.0.1:6379> 127.0.0.1:6379> # zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9個成員 127.0.0.1:6379> ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9 (integer) 9# zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9個成員 127.0.0.1:6379> ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9 (integer) 9# zcard key 獲取有序集合zset1的成員數 127.0.0.1:6379> ZCARD zset1 (integer) 9# zcount key min max 根據分數返回對應的成員列表 127.0.0.1:6379> ZCOUNT zset1 1 4 (integer) 4# zinterstore desKey nurnkeys key1 [key2 key3 …] 求多個有序集合的交集,并且將結果保存到 des Key 中 127.0.0.1:6379> ZINTERSTORE des_key 2 zset1 zset2 (integer) 4# zlexcount key min max 求有序集合 zset1 成員值在 min 和 max 的范圍 [ 表示包含該值,( 表示不包含該值 127.0.0.1:6379> ZLEXCOUNT zset1 (x1 [x5 (integer) 4# zrange key start stop [withscores] 按照分值的大小(從小到大)返回成員,加入 start 和 stop 參數可以截取某一段返回.如果輸入可選項 withscores,則連同分數一起返回 127.0.0.1:6379> ZRANGE zset1 1 5 withscores1) "x2"2) "2"3) "x3"4) "3"5) "x4"6) "4"7) "x5"8) "5"9) "x6" 10) "6"# zrank key member 按從小到大求有序集合的排行 127.0.0.1:6379> ZRANK zset1 x5 (integer) 4# zrangebylex key min max [limit offset count]根據值的大小,從小到大排序 [表示包含該值 (表示不包含該值 127.0.0.1:6379> ZRANGEBYLEX zset1 (x1 [x6 1) "x2" 2) "x3" 3) "x4" 4) "x5" 5) "x6" 127.0.0.1:6379> # zrangebyscore key min max [withscores] [limit offset count] 根據分數大小,從小到大求取范圍 127.0.0.1:6379> ZRANGEBYSCORE zset1 5 7 1) "x5" 2) "x6" 3) "x7"# zrangebyscore key min max [withscores] [limit offset count] 根據分數大小,從小到大求取范圍 127.0.0.1:6379> ZRANGEBYSCORE zset1 2 7 withscores limit 1 5 1) "x3"2) "3"3) "x4"4) "4"5) "x5"6) "5"7) "x6"8) "6"9) "x7" 10) "7"# zrevrange key start stop [withscores] 從大到小的按分數排序 127.0.0.1:6379> ZREVRANGE zset1 1 5 1) "x8" 2) "x7" 3) "x6" 4) "x5" 5) "x4"# zrevrangebyscore key max min [withscores] 從大到小的按分數排序 127.0.0.1:6379> ZREVRANGEBYSCORE zset2 5 2 withscores 1) "y5" 2) "5" 3) "x4" 4) "4" 5) "y3" 6) "3" 7) "x2" 8) "2"# zrevrank key member 按從大到小的順序,求元素的排行 127.0.0.1:6379> ZREVRANK zset1 x4 (integer) 5# zscore key member 返回成員的分數值 127.0.0.1:6379> ZSCORE zset1 x5 "5"# zunionstore desKey numKeys key1 [key2 key3 key4 …] 求多個有序集合的并集,其中 numKeys是有序,集合的個數 127.0.0.1:6379> ZUNIONSTORE des_key 2 zset1 zset2 (integer) 14# zincrby key increment member 給有序集合成員值為 member 的分數增加 increment 127.0.0.1:6379> ZINCRBY zset1 5 x9 "14"# zremrangebyscore key start stop 根據分數區間進行刪除 127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 2 (integer) 0# zremrangebyscore key start stop 根據分數區間進行刪除 127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 5 (integer) 3# zremrangebyrank key start stop 按照分數排行從小到大的排序刪除,從0開始計算 127.0.0.1:6379> ZREMRANGEBYRANK zset1 1 3 (integer) 3# zremrangebylex key min max 按照值的分布進行刪除 127.0.0.1:6379> ZREMRANGEBYLEX zset2 [y1 [y5 (integer) 6 127.0.0.1:6379> 127.0.0.1:6379> ZCARD zset1 (integer) 3 127.0.0.1:6379> ZCARD zset2 (integer) 3 127.0.0.1:6379> 127.0.0.1:6379> ZRANGE zset1 0 999 1) "x1" 2) "x8" 3) "x9" 127.0.0.1:6379> ZRANGE zset2 0 999 1) "y7" 2) "x8" 3) "y9" 127.0.0.1:6379>spring-data-redis 對有序集合的封裝
在 Spring 中使用 Redis 的有序集合,需要注意的是 Spring 對 Redis 有序集合的元素的值和分數的范圍( Range )和限制( Limit)進行了封裝。
我們來看下Spring是如何封裝的。 先介紹一個主要的接口一一TypedTuple,它不是一個普通的接口,而一個內部接口.
org.springframework . data. redis.core .ZSetOperations 接口的內部接口,它定義了兩個方
法
- getValue()是獲取值, getScore()是獲取分數,但是它只是一個接口,而不是一個實現類
- spring-data-red is 提供了 一個默認的實現類一DefaultTypedTuple
在默認的情況下 Spring 就會把帶有分數的有序集合的值和分數封裝到這個類中 ,這樣就可以通過這個類對象讀取對應的值和分數了 .
Spring 不僅對有序集合元素封裝,而且對范圍也進行了封裝,方便使用.它是使用接口 org.springframe.work.data.redis.connection.RedisZSetCommands 下的內部類 Range 進行封裝的,它有一個靜態的 range()方法,使用它就可以生成一個 Range 對象了,只是要清楚 Range對象的幾個方法才行.
這 4 個方法就是最常用的范圍方法.
下面看一下limit,它是接口 org.springframework.data.redis.connection.RedisZSetCommands 下的內部類,它是一個簡單的 POJO,它存在兩個屬性
通過屬性的名稱很容易知道:offset 代表從第幾個開始截取,而 count 代表限制返回的總數量。
使用 Spring 操作有序集合
剛才討論了 spring-data-redis 項目對有序集合的封裝,在此基礎上這里的演示示例代碼在測試代碼前,要把 RedisTemplate 的 keySerializer 和 valueSerializer屬性都修改為字符串序列化器 StringRedisSerializer
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="classpath:redis/redis.properties" /><!--2,注意新版本2.3以后,JedisPoolConfig的property name,不是maxActive而是maxTotal,而且沒有maxWait屬性,建議看一下Jedis源碼或百度。 --><!-- redis連接池配置 --><bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><!--最大空閑數 --><property name="maxIdle" value="${redis.maxIdle}" /><!--連接池的最大數據庫連接數 --><property name="maxTotal" value="${redis.maxTotal}" /><!--最大建立連接等待時間 --><property name="maxWaitMillis" value="${redis.maxWaitMillis}" /><!--逐出連接的最小空閑時間 默認1800000毫秒(30分鐘) --><property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" /><!--每次逐出檢查時 逐出的最大數目 如果為負數就是 : 1/abs(n), 默認3 --><property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" /><!--逐出掃描的時間間隔(毫秒) 如果為負數,則不運行逐出線程, 默認-1 --><property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" /><property name="testOnBorrow" value="true"></property><property name="testOnReturn" value="true"></property><property name="testWhileIdle" value="true"></property></bean><!--redis連接工廠 --><bean id="jedisConnectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"destroy-method="destroy"><property name="poolConfig" ref="jedisPoolConfig"></property><!--IP地址 --><property name="hostName" value="${redis.host.ip}"></property><!--端口號 --><property name="port" value="${redis.port}"></property><!--如果Redis設置有密碼 --><property name="password" value="${redis.password}" /> <!--客戶端超時時間單位是毫秒 --><property name="timeout" value="${redis.timeout}"></property><property name="usePool" value="true" /><!--<property name="database" value="0" /> --></bean><!-- 鍵值序列化器設置為String 類型 --><bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/><!-- redis template definition --><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"p:connection-factory-ref="jedisConnectionFactory"p:keySerializer-ref="stringRedisSerializer"p:defaultSerializer-ref="stringRedisSerializer"p:valueSerializer-ref="stringRedisSerializer"></bean></beans> package com.artisan.redis.baseStructure.zset;import java.util.HashSet; import java.util.Iterator; import java.util.Set;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.core.DefaultTypedTuple; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.data.redis.core.ZSetOperations.TypedTuple;public class SpringRedisZSetDemo {private static final String ZSET1 = "zset1";private static final String ZSET2 = "zset2";@SuppressWarnings({ "unchecked", "rawtypes" })public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/spring-redis-zset.xml");RedisTemplate redisTemplate = (RedisTemplate) ctx.getBean("redisTemplate");// 方便測試,清空數據redisTemplate.delete(ZSET1);redisTemplate.delete(ZSET2);// Spring 提供接口 TypedTuple 操作有序集合Set<TypedTuple> set1 = new HashSet<ZSetOperations.TypedTuple>();Set<TypedTuple> set2 = new HashSet<ZSetOperations.TypedTuple>();// 構造數據// 127.0.0.1:6379>// # zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9個成員// >ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9// (integer) 9// # zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9個成員// > ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9// (integer) 9int j = 9;String value1, value2 = null;double score1, score2 = 0.0;for (int i = 1; i <= 9; i++) {// 計算分數和值score1 = Double.valueOf(i);value1 = "x" + i;if (j > 0) {score2 = Double.valueOf(j);value2 = j % 2 == 1 ? "y" + j : "x" + j;j--;}// 使用 Spring提供的默認 TypedTuple-DefaultTypedTupleTypedTuple typedTuplel = new DefaultTypedTuple(value1,score1);set1.add(typedTuplel);TypedTuple typedTuple2 = new DefaultTypedTuple(value2,score2);set2.add(typedTuple2);}// 寫入redisredisTemplate.opsForZSet().add(ZSET1, set1);redisTemplate.opsForZSet().add(ZSET2, set2);// 統計總數Long size = redisTemplate.opsForZSet().size(ZSET1);System.out.println(ZSET1 + "的size為" + size);// 計分數為 score ,那么下面的方法就是求 3<=score<=6 的元素Long count = redisTemplate.opsForZSet().count(ZSET1, 3, 6);System.out.println(ZSET1 + "中3<=score<=6 的count為" + count);// 從下標一開始截馭 5 個元素,但是不返回分數 , 每一個元素是 StringSet set = redisTemplate.opsForZSet().range(ZSET1, 1, 5);printSet(set);// 截取集合所有元素,并且對集合按分數排序,并返回分數 , 每一個元素是 TypedTupleSet<TypedTuple> typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);printTypedTuple(typedTuples);// 將 zsetl 和 zset2 兩個集合的交集放入集合 inter_zsetsize = redisTemplate.opsForZSet().intersectAndStore(ZSET1, ZSET2, "inter_zset");System.out.println("inter_zset size:" + size);// 查看交集inter_zset中的數據set = redisTemplate.opsForZSet().range("inter_zset", 0, redisTemplate.opsForZSet().size("inter_zset"));printSet(set);// 區間Range range = Range.range();range.lt("x8");// 小于range.gt("x1");// 大于set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);printSet(set);range.lte("x8");// 小于等于range.gte("x1");// 大于等于set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);printSet(set);// 限制返回個數Limit limit = Limit.limit();// 限制返回個數limit.count(4);// 限制從第2個開始截取limit.offset(2);// 求區間內的元素,并限制返回 4 條set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range, limit);printSet(set);// 求排行,排名第 1 返回 0 ,第 2 返回 1Long rank = redisTemplate.opsForZSet().rank(ZSET1, "x4");System.out.println("rank=" + rank);// 刪除元素 , 返回刪除個數size = redisTemplate.opsForZSet().remove(ZSET1, "x5", "x6");System.out.println("remove " + size + " 個元素");// 按照排行刪除從 0 開始算起,這里將刪除第排名第 2 和第 3 的元素size = redisTemplate.opsForZSet().removeRange(ZSET1, 1, 2);System.out.println("removeRange " + size + " 個元素");// 獲取所有集合的元索和分數 , 以 -1 代表全部元素typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);printTypedTuple(typedTuples);// 刪除指定的元素size = redisTemplate.opsForZSet().remove(ZSET2, "y3", "y5");System.out.println("remove " + size + " 個元素");// 給集合中的一個元素的分數加上 11Double double1 = redisTemplate.opsForZSet().incrementScore(ZSET2, "y1", 11);printTypedTuple(redisTemplate.opsForZSet().rangeWithScores(ZSET2, 0, redisTemplate.opsForZSet().size(ZSET2)));// 從大到小排列typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(ZSET2, 0, 99);printTypedTuple(typedTuples);}@SuppressWarnings("rawtypes")public static void printTypedTuple(Set<TypedTuple> typedTuples) {if (typedTuples != null && typedTuples.isEmpty()) {return;}Iterator<TypedTuple> iterator = typedTuples.iterator();while (iterator.hasNext()) {TypedTuple typedTuple = iterator.next();System.out.println("{value =" + typedTuple.getValue() + ", score=" + typedTuple.getScore() + "}");}System.out.println("----------------------");}@SuppressWarnings("rawtypes")public static void printSet(Set set) {if (set != null && set.isEmpty()) {return;}Iterator iterator = set.iterator();while (iterator.hasNext()) {Object val = iterator.next();System.out.println(val + "\t");}System.out.println("----------------------");} }輸出
INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@73a8dfcc: startup date [Wed Sep 26 23:26:54 CST 2018]; root of context hierarchy INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring/spring-redis-zset.xml] zset1的size為9 zset1中3<=score<=6 的count為4 x2 x3 x4 x5 x6 ---------------------- {value =x1, score=1.0} {value =x2, score=2.0} {value =x3, score=3.0} {value =x4, score=4.0} {value =x5, score=5.0} {value =x6, score=6.0} {value =x7, score=7.0} {value =x8, score=8.0} {value =x9, score=9.0} ---------------------- inter_zset size:4 x2 x4 x6 x8 ---------------------- x2 x3 x4 x5 x6 x7 ---------------------- x1 x2 x3 x4 x5 x6 x7 x8 ---------------------- x3 x4 x5 x6 ---------------------- rank=3 remove 2 個元素 removeRange 2 個元素 {value =x1, score=1.0} {value =x4, score=4.0} {value =x7, score=7.0} {value =x8, score=8.0} {value =x9, score=9.0} ---------------------- remove 2 個元素 {value =x2, score=2.0} {value =x4, score=4.0} {value =x6, score=6.0} {value =y7, score=7.0} {value =x8, score=8.0} {value =y9, score=9.0} {value =y1, score=12.0} ---------------------- {value =y1, score=12.0} {value =y9, score=9.0} {value =x8, score=8.0} {value =y7, score=7.0} {value =x6, score=6.0} {value =x4, score=4.0} {value =x2, score=2.0} ----------------------注意
使用 Spring 提供的 RedisTemplate 去展示多個命令可以學習到如何使用 RedisTemplate 操作 Redis 。 實際工作中并不是那么用的,因為每一 個操作會嘗試從連接池里獲取 一 個新的 Redis 連接,多個命令應該使用SessionCallback 接口進行操作 。
代碼
代碼托管到了 https://github.com/yangshangwei/redis_learn
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Redis-07Redis数据结构--有序集合ZSet的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis-06Redis数据结构--集
- 下一篇: Redis-08Redis数据结构--基