Redis学习笔记之七:有序集合类型

 

    Redis最后一种类型是有序集合类型ZSet,即排序的Set,但又与Set不同的是,它比Set多一个字段分数(score)用于排序等操作,从这点来看,相当于Java中的TreeMap,但与Java的TreeMap不同的是,TreeMap排序是指定Comparator对象来排序,通过比较Key来排序,最后构成一棵树。而Redis的ZSet在存储结构上类似于Set。

    1、设值/取值

    使用ZADD指令进行设值。格式ZADD key score value1 score value2...,返回值是成功添加元素的个数。演示如下:score必须是字符串形式的数值类型

    可以看到score必须是一个有效的float类型。而且score的值是可以重复的,但Key的值不能重复。从这点来看与散列类型很相似,只不过在添加时把Key-Value的顺序颠倒了,而且Value还必须是字符串形式的整数。

    ZSCORE指令可以取出对应的值,如上图,取出了Redis中Key为myzset对应的有序集合中Key为b的score。

    在Redis中+inf表示正无穷,-inf表示负无穷。

    2、排序功能(1)

    ZRANGE指令用于获取某一范围的元素并根据score进行从小到大的排序,如果分数一致,则会比较Key,按照字典排序。这个指令与LRANGE指令是一致的。

扫描二维码关注公众号,回复: 12657973 查看本文章

    其中withscores表示是否显示分数,带上这个参数则单行为Key,双行为score。

    ZREVRANGE与ZRANGE指令一致,可以看成reverse ZRANGE的缩写,所以这个指令是从大到小。

    3、获取有序集合中元素的个数

    指令ZCARD用于获取有序集合中元素的个数,想到了Set的SCARD吧,所以从指令就可以看出这两种数据类型很类似。

    

    4、获取指定分数范围内元素的个数

    ZCOUNT指令用于获取分数在某个范围内的元素个数,格式ZCOUNT zset minScore maxScore。

    可以看到如果minScore大于maxScore是返回0的。

    5、删除元素

    ZREM指令用于删除元素,格式ZREM zset key1 key2.返回值为实际删除元素个数。

    

    6、按照分数范围删除元素

    ZREMRANGEBYSCORE指令用于删除某个分数范围的元素。

    格式ZREMRANGEBYSCORE zset minScore maxScore.

    可以看到如果minScore大于maxScore,则指令不会删除任何元素。

    7、按照分数排名范围删除元素

    ZREMRANGEBYRAMK指令用于删除分数从小打大排列的前几位元素。

    格式ZREMRANGEBYRANK zset start stop。支持负索引,演示如下:

    8、获取元素的排名

    ZRANK指令用于获取分数从小到大元素的排名,而ZREVRANK则相反。

    Redis的索引是从0开始的。

    

    9、获取指定分数范围的元素。

    ZRANGEBYSCORE指令用于获取指定分数范围的前多少个元素。

    格式ZRANGEBYSCORE zset minScore maxScore [WITHSCORES] [limit offset count],演示如下:

    获取分数从小到大排序,分数在[20 - 80]之间的元素,然后输出分数,并且只取索引从1开始的3个元素(索引从0开始)。

    注意,ZRANGE与ZREVRANGE是取分数排序后相应索引位置的数据,0代表第一个,-1代表最右边的元素(负索引),没有像这个命令需要指定分数范围。但ZRANGE zset 0 -1 可以认为与ZRANGEBYSCORE zset -inf +inf limit 0 -1等同。同样minScore大于maxScore返回empty。如下图演示:

    与之相对应的是ZREVRANGEBYSCORE指令,格式略有不同

    ZREVRANGEBYSCORE zset maxScore minScore [withscores] [limit offset count],演示如下

    这儿特别要注意的是,ZRANGE和ZREVRANGE指令后面跟的是索引,且是上包含[]关系,如ZRANGE zset 0 3,是取索引从0到3的元素,包括0和3.而ZRANGEBYSCORE后面是limit,例如ZRANGEBYSCORE .... limit 0 3, 表示的是从索引为0的位置开始,一共取3个元素,如果count为负数(这儿举的例子是3),则相当于从该索引后的所有元素都会被取出来。例子如下:

    10、增加某个值的分数

    ZINCRBY指令用于增加某个Key对应的Score,指令格式ZINCRBY zset score key。如果Key不存在则相当于zadd指令。如果在Redis中存储文章或者新闻的访问量,没访问一次访问量+1,那这个命令非常有用。演示如下:

    

    11、有序集合之间的运算

    指令ZINTERSTORE用于计算有序集合的交集并保存在目标集合内。

    ZINTERSTORE desSet numberSets zset1 zset2 zset3 [WIGHTS weight1 weight2] [aggregate SUM|MIN|MAX]

    会有numberSets个有序集合参与计算,会依次将zset1, zset2,zset3进行交集运算,参考Set的SINTER指令,然后将所得交集存放进desSet中,那所得的分数怎么办呢?后面的WEIGHTS权重会依次与前面的zset对应,然后zset的分数会乘以权重得到分数就是这个分数,然后根据SUM|MIN|MAX采取相应的操作,演示如下:

    此外还有ZUNIONSTORE指令求并集操作。


Redis的Java SDK

    测试代码如下(略长):

 
  1. package org.yamikaze.redis.test;

  2.  
  3. import org.junit.After;

  4. import org.junit.Before;

  5. import org.junit.Test;

  6. import redis.clients.jedis.Jedis;

  7. import redis.clients.jedis.Tuple;

  8. import redis.clients.jedis.params.sortedset.ZAddParams;

  9.  
  10. import java.util.HashMap;

  11. import java.util.HashSet;

  12. import java.util.Map;

  13. import java.util.Set;

  14.  
  15. import static org.junit.Assert.*;

  16.  
  17. /**

  18. * 使用Redis的Java SDK测试Zset类型

  19. * @author yamikaze

  20. */

  21. public class ZSetTest {

  22.  
  23. private Jedis client;

  24. private static final String KEY = "myzset";

  25. private String key1 = "key1";

  26. private double score1 = 20;

  27. private Map<String, Double> map;

  28.  
  29.  
  30. @Before

  31. public void setUp() {

  32. client = MyJedisFactory.defaultJedis();

  33. map = new HashMap<String, Double>(16);

  34. map.put(key1, score1);

  35. map.put("key2", 20d);

  36. map.put("key3", 30d);

  37. map.put("key4", 40d);

  38. map.put("key5", 50d);

  39. map.put("key6", 60d);

  40. }

  41.  
  42. /**

  43. * 测试ZADD,返回值是插入有序集合元素的个数

  44. * 由于SDK中含有ZAddParams对象参数。

  45. * 这个参数会改变zadd指令的行为,且一旦绑定了ZAddParams对象就不能再绑定

  46. * 所以分为4个测试方法测试ZADD

  47. */

  48. @Test

  49. public void testZAddWithoutZAddParams() {

  50. long size = client.zadd(KEY, 20, key1);

  51. assertTrue(size == 1);

  52.  
  53. //有相同的Key时不会做插入,会做更新

  54. size = client.zadd(KEY, 30, key1);

  55. assertTrue(size == 0);

  56.  
  57. //插入多个元素

  58. size = client.zadd(KEY, map);

  59. assertTrue(size == map.size() -1);

  60. }

  61.  
  62. /**

  63. * 这儿顺带将ZSCORE指令测试了

  64. */

  65. @Test

  66. public void testZAddWithZAddParams01() {

  67. long size = client.zadd(KEY, map);

  68. assertTrue(size == map.size());

  69.  
  70. //指定参数ZAddParams, 经过上面的运行key1对应的score为20

  71. ZAddParams zp = ZAddParams.zAddParams();

  72. //nx表示如果key1不存在则插入,所以下面score为20,没有变为30

  73. zp.nx();

  74. client.zadd(KEY, 30, key1, zp);

  75. double score = client.zscore(KEY, key1);

  76. assertTrue(score == 20);

  77. }

  78.  
  79. @Test

  80. public void testZAddWithZAddParams02() {

  81. long size = client.zadd(KEY, map);

  82. assertTrue(size == map.size());

  83.  
  84. //指定参数ZAddParams, 经过上面的运行key1对应的score为20

  85. ZAddParams zp = ZAddParams.zAddParams();

  86. //xx表示如果key存在才作插入(更新),否则不做插入

  87. zp.xx();

  88. client.zadd(KEY, 30, "1234", zp);

  89. //不存在,为空,表示上面的语句没有做插入

  90. //如果使用语句double score来接收结果,会抛出NPE

  91. //如果是double score声明记得在Test上面加上(expected = NullPointerException.class)

  92. Double score = client.zscore(KEY, "1234");

  93. assertNull(score);

  94. }

  95.  
  96. @Test

  97. public void testZAddWithZAddParams03() {

  98. long size = client.zadd(KEY, map);

  99. assertTrue(size == map.size());

  100.  
  101. //指定参数ZAddParams, 经过上面的运行key1对应的score为20

  102. ZAddParams zp = ZAddParams.zAddParams();

  103. //ch表示返回被修改的元素个数

  104. zp.ch();

  105. //score被更新

  106. size = client.zadd(KEY, 50, key1, zp);

  107. assertTrue(size == 1);

  108.  
  109. //返回0,既没有更新score,也没有插入

  110. size = client.zadd(KEY, 50, key1, zp);

  111. assertTrue(size == 0);

  112.  
  113. //插入新元素

  114. size = client.zadd(KEY, 50, "12345", zp);

  115. assertTrue(size == 1);

  116. }

  117.  
  118. /**

  119. * 测试ZCARD指令,返回有序集合的元素数

  120. */

  121. @Test

  122. public void testZCard() {

  123. long size = client.zadd(KEY, map);

  124. assertTrue(size == map.size());

  125.  
  126. Long count = client.zcard(KEY);

  127. assertTrue(count.equals(size));

  128. }

  129.  
  130. /**

  131. * 测试ZCOUNT指令

  132. * 用于返回某个分数范围内的元素个数

  133. * 20 20 30 40 50 60(map中的分数)

  134. */

  135. @Test

  136. public void testZCount() {

  137. long size = client.zadd(KEY, map);

  138. assertTrue(size == map.size());

  139.  
  140. //20~50之间的个数应该是map的size()-1, 要包括20与50

  141. long count = client.zcount(KEY, 20d, 50d);

  142. assertTrue(count == map.size() -1);

  143. }

  144.  
  145. /**

  146. * 测试ZRANGE与ZREVRANGE

  147. */

  148. @Test

  149. public void testZRangeAndZRevRange() {

  150. long size = client.zadd(KEY, map);

  151. assertTrue(size == map.size());

  152.  
  153. Set<String> zranges = client.zrange(KEY, 0, -1);

  154. Set<String> zrevranges = client.zrevrange(KEY, 0, -1);

  155. assertNotNull(zranges);

  156. assertNotNull(zrevranges);

  157. assertTrue(zranges.size() == zrevranges.size());

  158. for(String key: zranges) {

  159. assertTrue(zrevranges.contains(key));

  160. }

  161.  
  162. for(String key: zrevranges) {

  163. assertTrue(zranges.contains(key));

  164. }

  165. }

  166.  
  167. /**

  168. * 测试ZRANGE与ZREVRANGE带有WITHSCORES参数

  169. */

  170. @Test

  171. public void testZRangeAndZRevRangeWithScores() {

  172. long size = client.zadd(KEY, map);

  173. assertTrue(size == map.size());

  174.  
  175. //ZRANGE与ZREVRANGE各取一半,map的size为6

  176. Set<Tuple> zranges = client.zrangeWithScores(KEY, 0, 2);

  177. Set<Tuple> zrevranges = client.zrevrangeWithScores(KEY, 0, 2);

  178. assertNotNull(zranges);

  179. assertNotNull(zrevranges);

  180. assertTrue(zranges.size() == zrevranges.size());

  181. assertTrue(zranges.size() + zrevranges.size() == map.size());

  182.  
  183. Set<String> keys = new HashSet<String>(8);

  184. isEquals(zranges, map, keys);

  185. isEquals(zrevranges, map, keys);

  186. assertTrue(keys.size() == map.size());

  187. }

  188.  
  189. private void isEquals(Set<Tuple> tuples, Map<String, Double> maps, Set<String> keys) {

  190. for(Tuple tuple : tuples) {

  191. String key = tuple.getElement();

  192. double score = tuple.getScore();

  193. double mapScore = map.get(key);

  194. assertTrue(map.containsKey(key));

  195. assertTrue(score == mapScore);

  196. keys.add(key);

  197. }

  198. }

  199.  
  200. /**

  201. * 测试ZRANGEBYSCORE和ZRAVRANGEBYSCORE

  202. */

  203. @Test

  204. public void testZRangeByScore() {

  205. long size = client.zadd(KEY, map);

  206. assertTrue(size == map.size());

  207.  
  208. //SDK并没有提供带有Limit的方法,只有zrangeByScoreWithScores方法

  209. Set<String> ranges = client.zrangeByScore(KEY, 20, 30);

  210. Set<String> revranges = client.zrevrangeByScore(KEY, 60, 40);

  211.  
  212. assertNotNull(ranges);

  213. assertNotNull(revranges);

  214. assertTrue(ranges.size() == revranges.size());

  215.  
  216. for(String str: ranges) {

  217. assertFalse(revranges.contains(str));

  218. }

  219.  
  220. for(String str: revranges) {

  221. assertFalse(ranges.contains(str));

  222. }

  223. }

  224.  
  225. /**

  226. * 测试ZREM删除元素

  227. */

  228. @Test

  229. public void testZRem() {

  230. long size = client.zadd(KEY, map);

  231. assertTrue(size == map.size());

  232.  
  233. long remSize = client.zrem(KEY, key1, "abc");

  234. assertTrue(remSize == 1);

  235. }

  236.  
  237. /**

  238. * 测试ZREMRANGEBYSCORE,按照分数范围删除元素

  239. */

  240. @Test

  241. public void testZRemRangeByScore() {

  242. long size = client.zadd(KEY, map);

  243. assertTrue(size == map.size());

  244.  
  245. //20 20 30 40 50 60,执行完以下语句集合中应该只剩下一个元素

  246. long remSize = client.zremrangeByScore(KEY, 20, 50);

  247. assertTrue(remSize == map.size() - 1);

  248.  
  249. long count = client.zcard(KEY);

  250. assertTrue(count == 1);

  251. }

  252.  
  253. /**

  254. * 测试ZREMRANGEBYRANK,按照排名删除元素

  255. */

  256. @Test

  257. public void testZRemRangeByRank() {

  258. long size = client.zadd(KEY, map);

  259. assertTrue(size == map.size());

  260.  
  261. //删除 20 20 30 集合中还剩40 50 60

  262. long remSize = client.zremrangeByRank(KEY, 0, 2);

  263. //如果集合中的元素本身就不足指定的个数,那么将会全部删除

  264. assertTrue(remSize == 3);

  265.  
  266. Double score = client.zscore(KEY, key1);

  267. assertNull(score);

  268.  
  269. //删除最大的几个元素

  270. size = client.zadd(KEY, map);

  271. assertTrue(size == 3);

  272.  
  273. //从指定的位置开始删除,如果start指定为-1,已经是最后一个元素了,无法删除

  274. remSize = client.zremrangeByRank(KEY, -2, -1);

  275. //如果集合中的元素本身就不足指定的个数,那么将会全部删除

  276. assertTrue(remSize == 2);

  277.  
  278. score = client.zscore(KEY, "key6");

  279. assertNull(score);

  280.  
  281. score = client.zscore(KEY, "key5");

  282. assertNull(score);

  283.  
  284. }

  285.  
  286. /**

  287. * ZRANK获取元素的排名

  288. */

  289. @Test

  290. public void testZRank() {

  291. long size = client.zadd(KEY, map);

  292. assertTrue(size == map.size());

  293. //ZRANK,从小到大的排名 从0开始

  294. long rank = client.zrank(KEY, key1);

  295. assertTrue(rank == 0);

  296.  
  297. //key1 与key2的分数一致,然后按照字典排序,所以key2为1

  298. rank = client.zrank(KEY, "key2");

  299. assertTrue(rank != 0);

  300. assertTrue(rank == 1);

  301.  
  302. //ZRENRANK 从大到小的排名

  303. rank = client.zrevrank(KEY, key1);

  304. assertTrue(rank == 5);

  305.  
  306. //不存在的key的排名为null

  307. Long rank1 = client.zrank(KEY, "abc");

  308. assertNull(rank1);

  309. }

  310.  
  311. /**

  312. * 篇幅原因,运算指令略

  313. */

  314.  
  315. @After

  316. public void tearDown() {

  317. if(client != null) {

  318. client.del(KEY);

  319. }

  320. }

  321. }


总结

   1)、Redis的ZSet算是Redis 5种Value最高级的一种了,在指令上类似于操纵Value的Map。与Map操纵Key刚好颠倒了。除此之外,特性也与Java的Map一致,可以将ZSet看成java Map,但这个Map类型被固定成了Map<String, Double>类型,可以看到上面的测试代码,添加多个Key-Score时就是用的这种Map形式。

   

   2)、ZSet类型的指令繁多,至少算是5种类型最多的,上面列出的指令并不是ZSet指令的全部。而且有些指令不便于记忆,例如ZRANGEBYSCORE,ZREVRANGEBYSCORE、ZREMRANGEBYSCORE等相似的指令容易混淆。且也无指令得到ZSet的某个Key是否存在,当然你可以使用ZSCORE指令,如果返回为Null,则不存在,同理ZRANK指令也行。

   3)、Redis的SDK并没有Redis提供的指令那样强大,例如zrangebyscore指令有limit限制,而在SDK中没有。

   

   最后Redis的各个类型的指令有那么多,但实际上有的指令并不是很常用,例如LRANGE, ZRANGE,SMEMBERS等需要遍历整个列表或集合的指令,因为元素比较多时,执行这些指令相当耗费时间,尤其是LRANGE指令。

参考资料

   《Redis入门指南》

猜你喜欢

转载自blog.csdn.net/u011250186/article/details/113320445