redis-07 五大基本类型之ZSet
redis-07 五大基本类型之ZSet
概述
Redis 中的有序集合在集合的基础上为每个元素都关联了一个分数,有了分数之后我们就能依据分数进行相应的操作(排序、插入、删除……),这一点似乎和链表很像,但是两者的区别却很大:
- List 基于双向链表实现,而有序集合基于跳表实现(读取复杂度低);
- List 中改变元素的位置很是麻烦(该表前驱节点啊、后续节点啊什么的),但是有序集合只需要改变元素的分数就能改变其位置。
命令
增、删、改、查
- zadd
- 新增或者修改(已存在的元素)元素和分数
- zrem
- zscore
- 查看元素分数
@Test
public void cURD() {
long effected = j.zadd("fruit", 1, "apple");
assertEquals(1L, effected);
HashMap<String, Double> map = new HashMap<>();
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
effected = j.zadd("fruit", map);
assertEquals(3L, effected);
// 根据值删除元素
effected = j.zrem("fruit", "banana", "watermelon");
assertEquals(2L, effected);
// 修改元素分数
effected = j.zadd("fruit", 2, "apple");
assertEquals(0L, effected); // 注意这里改变了元素的 score,但是返回值确是 0
// 根据元素值查看分数
Double appleScore = j.zscore("fruit", "apple");
assertTrue(appleScore - 2D < 0.01D);
}
根据排名获取元素
- zrange
- zrevrange
public void getElementsByRankIndex() {
HashMap<String, Double> map = new HashMap<>();
map.put("apple", 1D);
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
long effected = j.zadd("fruit", map);
assertEquals(4L, effected);
// 根据分数的排名从低到高选取指定范围的元素
Set<String> fruit_0_3 = j.zrange("fruit", 0, 2);
assertNotNull(fruit_0_3);
assertEquals(3, fruit_0_3.size());
assertTrue(fruit_0_3.contains("apple"));
assertTrue(fruit_0_3.contains("banana"));
assertTrue(fruit_0_3.contains("orange"));
// 根据分数的排名从高到低选取指定范围的元素
fruit_0_3 = j.zrevrange("fruit", 0, 2);
assertNotNull(fruit_0_3);
assertEquals(3, fruit_0_3.size());
assertTrue(fruit_0_3.contains("banana"));
assertTrue(fruit_0_3.contains("orange"));
assertTrue(fruit_0_3.contains("watermelon"));
}
根据分数获取元素
- zrangeByScore
- zrangeByScoreWithScores
- 同时返回元素值和分数
@Test
public void getElementsByScore() {
HashMap<String, Double> map = new HashMap<>();
map.put("apple", 1D);
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
long effected = j.zadd("fruit", map);
assertEquals(4L, effected);
// 根据分数的排名选取指定范围的元素,包含端点
Set<String> fruit_0_3 = j.zrangeByScore("fruit", "1", "3");
assertNotNull(fruit_0_3);
assertEquals(3, fruit_0_3.size());
assertTrue(fruit_0_3.contains("apple"));
assertTrue(fruit_0_3.contains("banana"));
assertTrue(fruit_0_3.contains("orange"));
// 不包含端点
Set<String> fruit_2 = j.zrangeByScore("fruit", "(1", "(3");
assertNotNull(fruit_2);
assertEquals(1, fruit_2.size());
assertTrue(fruit_2.contains("banana"));
// 无穷 inf
Set<String> fruit_2_inf = j.zrangeByScore("fruit", "(1", "+inf");
assertNotNull(fruit_2_inf);
assertEquals(3, fruit_2_inf.size());
assertTrue(fruit_2_inf.contains("banana"));
assertTrue(fruit_2_inf.contains("orange"));
assertTrue(fruit_2_inf.contains("watermelon"));
// 带着 score 一起返回
Set<Tuple> fruit_2_inf_tuple = j.zrangeByScoreWithScores("fruit", "(1", "+inf");
assertTrue(fruit_2_inf_tuple.contains(new Tuple("banana", 2D)));
assertTrue(fruit_2_inf_tuple.contains(new Tuple("orange", 3D)));
assertTrue(fruit_2_inf_tuple.contains(new Tuple("watermelon", 4D)));
}
增加元素分数
- zincrby
- 提升元素分数
@Test
public void incrScore() {
HashMap<String, Double> map = new HashMap<>();
map.put("apple", 1D);
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
long effected = j.zadd("fruit", map);
assertEquals(4L, effected);
Double appleScore = j.zincrby("fruit", 1.1, "apple"); // 返回新的 score
assertTrue(appleScore - 2.1 < 0.01);
appleScore = j.zincrby("fruit", -1.0, "apple"); // 返回新的 score
assertTrue(appleScore - 1.1 < 0.01);
}
统计、删除指定范围元素
- zcard
- zcount
- 统计指定分数范围内元素个数
- zremrangeByScore
- zrank
- 获取元素排名,从 0 开始
@Test
public void getCardOfZSet() {
HashMap<String, Double> map = new HashMap<>();
map.put("apple", 1D);
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
long effected = j.zadd("fruit", map);
assertEquals(4L, effected);
long card = j.zcard("fruit");
assertEquals(4L, card);
// 按照排名范围删除元素
effected = j.zremrangeByRank("fruit", 3L, 3L);
assertEquals(1L, effected);
// 根据 score 范围统计元素个数
long count = j.zcount("fruit", "(1", "4");
assertEquals(2L, count);
// 按照 score 范围删除元素
effected = j.zremrangeByScore("fruit", "1", "1");
assertEquals(1L, effected);
// 返回元素排名,从小到大,最小是 0
long rank_banana = j.zrank("fruit", "banana");
assertEquals(0L, rank_banana);
// 返回元素排名,从大到小,最小是 0
rank_banana = j.zrevrank("fruit", "banana");
assertEquals(1L, rank_banana);
}
有序集合交集
- zinterstore
- 执行交集处理后存储到新的有序集合
- 可以指定交集内元素分数的设置
- 最大
- 最小
- 两相同元素之和
@Test
public void zSetInter() {
HashMap<String, Double> map = new HashMap<>();
map.put("apple", 1D);
map.put("banana", 2D);
map.put("orange", 3D);
map.put("watermelon", 4D);
long effected = j.zadd("fruit_1", map);
assertEquals(4L, effected);
map.remove("apple");
map.remove("orange");
effected = j.zadd("fruit_2", map);
assertEquals(2L, effected);
// fruit_1: "apple", 1D | "banana", 2D | "orange", 3D | "watermelon", 4D
// fruit_2: "banana", 2D | "watermelon", 4D
// 两个有序集合求交集,并且交集后的 score 为两者之和,还可以设置成两者间的最小值、最大值
ZParams zParams = new ZParams();
zParams.aggregate(ZParams.Aggregate.SUM);
long cardOfNewZSet = j.zinterstore("fruit_3", zParams, "fruit_1", "fruit_2");
assertEquals(2L, cardOfNewZSet);
double score_banana = j.zscore("fruit_3", "banana");
assertTrue(score_banana - 4D < 0.01);
}
实践
文章访问量
文章 ID 作为元素内容,文章访问量作为元素分数,每次访问就加一。
文章日期
文章 ID 作为元素内容,文章时间戳作为元素分数,可以实现修改和查看指定时间段的文章。