面试被问到Redis的Zset(Sorted Set)是什么?我懵了

目录

一. 基本概念

二. 跳跃表

跳跃表的基本结构

插入操作

查找操作

删除操作

跳跃表的优势

三. 应用场景

四. 优势与总结


一. 基本概念

有序集合(Sorted Set) 是Redis中一种有序的数据结构,它以集合的形式存储多个唯一的元素,每个元素关联一个分数(score),通过分数的排序,使得元素在集合中有序排列。有序集合的成员是唯一的,但分数可以重复。

在有序集合中,元素的排列是根据分数的大小进行的,这使得有序集合非常适用于需要排序和范围查询的场景。

有序集合的本质: 有序集合是Redis提供的一种有序数据结构,每个元素都有一个相关联的分数。这个分数用于排序,使得我们能够按照一定的规则轻松地检索和排列数据。

底层实现: Redis内部使用跳跃表(Skip List)和哈希表的混合结构来实现有序集合。这样的底层实现既保证了有序集合的有序性,又提供了高效的插入、删除和查找操作。

二. 跳跃表

跳跃表(Skip List)是一种数据结构,用于有序元素的快速搜索。它结合了链表和多层次的索引,以提高查找效率。跳跃表的设计目标是在维持有序性的同时,使得插入、删除和查找操作的时间复杂度都能够保持在 O(log n) 的水平。

跳跃表的基本结构:

  1. 节点(Node):

    • 节点包含一个元素值(key)以及指向同一层次上下一个节点的指针。
    • 在高层次上,节点还包含指向同一层次右边节点的指针。
  2. 层次(Level):

    • 跳跃表的每一层都是一个有序的链表。
    • 最底层包含所有元素,每一层都是前一层的子集。
  3. 头节点(Head Node):

    • 跳跃表有一个头节点,不包含任何元素值,只包含指向每一层最右节点的指针。

插入操作

在插入一个新元素时,跳跃表通过随机确定新元素的层数,以便将其插入到多个层次上,同时保持有序性。

  1. 选择插入的层数,通常使用随机算法确定。
  2. 在每一层中找到要插入位置的前一个节点。
  3. 创建新节点,将其插入到每一层的对应位置。

当向跳跃表插入新元素时,插入操作包括以下步骤:

  1. 选择插入的层数:

    • 在跳跃表中,每个节点都有多个层次。在插入元素时,通过随机算法决定新元素的层数,通常使用一种概率算法,例如随机翻硬币来决定层数。
    • 新节点的层数决定了它将被插入到跳跃表的多少层次中。
  2. 查找插入位置:

    • 从跳跃表的头节点开始,逐层向右移动,找到每一层中小于待插入元素的最大节点。
    • 这一步是为了找到插入元素的前一个节点,以便在新元素后插入。
  3. 执行插入操作:

    • 在每一层中,创建一个新的节点,将新元素插入到找到的位置后。
    • 更新相应的指针,使新节点与左右相邻的节点连接。
  4. 更新层次索引:

    • 对于新插入的节点,根据随机算法选择其实际层数,更新头节点指针,确保每一层都能够正确连接。

这样,通过在不同层次插入元素,跳跃表保持了有序性和层次结构。这种设计在查找操作时能够快速定位目标元素,从而保证了插入、删除和查找操作的高效性。

示例代码(伪代码):

import java.util.Random;

class Node {
    int value;
    Node[] right;

    public Node(int value, int level) {
        this.value = value;
        this.right = new Node[level];
    }
}

public class SkipList {
    private Node head;
    private int maxLevel;

    public SkipList(int maxLevel) {
        this.head = new Node(Integer.MIN_VALUE, maxLevel);
        this.maxLevel = maxLevel;
    }

    private int randomLevel() {
        Random random = new Random();
        int level = 1;
        while (random.nextInt(2) == 0 && level < maxLevel) {
            level++;
        }
        return level;
    }

    public void insert(int value) {
        int level = randomLevel();
        Node[] update = new Node[level];
        Node current = head;

        for (int i = level - 1; i >= 0; i--) {
            while (current.right[i] != null && current.right[i].value < value) {
                current = current.right[i];
            }
            update[i] = current;
        }

        Node newNode = new Node(value, level);

        for (int i = 0; i < level; i++) {
            newNode.right[i] = update[i].right[i];
            update[i].right[i] = newNode;
        }
    }
}

请注意,这是一个简化的版本,实际应用中可能需要更多的细节和完善的错误处理。这个示例主要用于演示插入操作的基本思想,可以根据具体需求进行扩展和优化。 

查找操作

查找操作在每一层上进行,从最顶层开始,逐层向右移动。如果在当前层找到了大于等于目标值的节点,就切换到下一层继续查找。这样,跳跃表可以快速定位到目标元素。

删除操作

删除操作与查找类似,需要在每一层找到要删除的节点,然后更新相应的指针。

跳跃表的优势:

  1. 简单而高效: 跳跃表的设计相对简单,插入、删除和查找操作的平均时间复杂度都是 O(log n)。

  2. 天然有序: 跳跃表天然有序,无需进行额外的排序操作。

  3. 支持范围查询: 由于每一层都是有序链表,范围查询非常高效。

三. 应用场景

1. 社交应用中的排行榜:

在社交应用中,用户积分是一个关键的指标,而有序集合可以非常轻松地应对这种情况。

// Java代码示例
String user1 = "Alice";
String user2 = "Bob";
String user3 = "Charlie";

Jedis jedis = new Jedis("localhost");

// 用户积分作为分数存入有序集合
jedis.zadd("leaderboard", 1000, user1);
jedis.zadd("leaderboard", 1200, user2);
jedis.zadd("leaderboard", 800, user3);

// 获取排名前两的用户
Set<Tuple> leaderboard = jedis.zrevrangeWithScores("leaderboard", 0, 1);
for (Tuple tuple : leaderboard) {
    System.out.println(tuple.getElement() + ": " + tuple.getScore());
}

在这个场景中,有序集合用于维护用户积分的排行榜。通过 zrevrangeWithScores 方法,我们能够方便地获取分数最高的用户,实现了一个动态且高效的排行榜系统。

2. 任务调度队列:

在任务调度系统中,任务可能有不同的优先级,而有序集合正是为这样的场景而设计的。

// Java代码示例
String task1 = "HighPriorityTask";
String task2 = "MediumPriorityTask";
String task3 = "LowPriorityTask";

Jedis jedis = new Jedis("localhost");

// 任务优先级作为分数存入有序集合
jedis.zadd("taskQueue", 3, task1);
jedis.zadd("taskQueue", 2, task2);
jedis.zadd("taskQueue", 1, task3);

// 获取最高优先级的任务
String highestPriorityTask = jedis.zrange("taskQueue", 0, 0).iterator().next();
System.out.println("Highest Priority Task: " + highestPriorityTask);

在这个例子中,我们使用有序集合来维护一个任务队列,任务的优先级通过分数表示。通过 zrange 方法,我们可以获取最高优先级的任务,以实现任务调度的高效管理。

3. 时间轴:

  • 场景描述: 在微博、社交应用中,需要展示用户发布的消息按时间顺序排列。
  • 有序集合应用: 每条消息作为有序集合的一个成员,其分数表示消息的发布时间戳。通过有序集合按时间排序,可以实现按时间轴展示用户的消息。

4. 范围查询:

  • 场景描述: 在地理位置服务中,需要查找附近的点或区域。
  • 有序集合应用: 地理位置坐标作为有序集合的成员,其分数表示距离。通过有序集合的范围查询,可以快速获取附近的地理位置信息。

5. 频道订阅数统计:

  • 场景描述: 在实时聊天或内容分享应用中,统计不同频道的订阅数。
  • 有序集合应用: 每个频道作为有序集合的一个成员,其分数表示订阅数。通过有序集合的分数排序,可以获取订阅数最高的频道。

示例代码(简化):

# 将用户的得分添加到有序集合中
ZADD leaderboard 1000 "user1"
ZADD leaderboard 1200 "user2"
ZADD leaderboard 800 "user3"

# 获取排名前两的用户
ZRANGE leaderboard 0 1 WITHSCORES

在上述示例中,leaderboard 是一个有序集合,每个用户作为成员,其得分表示分数。通过 ZRANGE 命令可以获取排名前两的用户和他们的得分。这展示了有序集合在排行榜等场景中的灵活应用。

四. 优势与总结

  1. 灵活性: 有序集合提供了灵活的数据排序和检索机制,使其适用于多种场景,包括排行榜、任务调度等。

  2. 高效性: Redis内部采用跳跃表和哈希表的结合实现,保证了对于插入、删除和查找的高效处理。

  3. 简洁性: 使用有序集合可以简化系统的设计,减少不必要的复杂性,提高代码的可读性。

  4. 实时性: 有序集合对于实时的排名和排序非常合适,适用于需要即时展示数据的场景。

在架构设计中,合理利用有序集合,可以有效提升系统的性能和响应速度,为用户提供更好的体验。这使得有序集合成为Java架构师工具箱中的一项强大工具。

猜你喜欢

转载自blog.csdn.net/weixin_43728884/article/details/134701356