Loading

redis-06 五大基本类型之ZSet

redis-06 五大基本类型之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 作为元素内容,文章时间戳作为元素分数,可以实现修改和查看指定时间段的文章。

posted @ 2021-10-07 00:07  槐下  阅读(92)  评论(0编辑  收藏  举报