图解PriorityQueue数据结构设计与应用案例

219 阅读5分钟

image.png

PriorityQueue 是 Java 集合框架中的一个优先级队列实现,它基于堆数据结构(通常为二叉堆)来维护元素的顺序。在 PriorityQueue 中,元素根据其自然顺序或者通过提供的 Comparator 来排序,确保队列头部(根节点)总是优先级最高的元素。这种数据结构适用于需要根据元素重要性或紧迫性进行处理的场景,如任务调度、事件驱动模拟、Dijkstra 算法等。由于其基于堆的实现,PriorityQueue 提供了高效的插入、删除和检索操作,具有对数时间复杂度。

PriorityQueue(业务结构)

PriorityQueue 是 Java 中的一个优先队列实现,通常基于堆数据结构(默认为二叉堆,也可以通过构造函数指定其他类型的堆,如斐波那契堆),PriorityQueue 维护了一个元素的集合,这些元素按照自然顺序或者通过提供的 Comparator 进行排序。

设计思考:

  1. 需求场景
    • 在许多编程任务中,需要根据元素的优先级来处理数据。例如,在任务调度、事件驱动模拟、资源分配等问题中,某些元素需要比其他元素更优先地被处理。
  2. 现有技术局限性
    • 标准的队列(如 LinkedList 实现的队列)仅按照元素的到达顺序进行处理,不支持基于优先级的排序。
    • 手动维护一个排序的列表来进行优先级处理会增加额外的复杂性和性能开销,尤其是在插入和删除操作频繁的场景中。
  3. 技术融合
    • PriorityQueue 结合了队列的先进先出(FIFO)特性和优先级排序的需要,使用一个优先级规则来决定元素的处理顺序。
  4. 设计理念
    • PriorityQueue 允许元素根据自然顺序或者通过提供的 Comparator 来决定优先级,确保每次取出的元素都是优先级最高的。
  5. 实现方式
    • PriorityQueue 通常基于堆数据结构(通常是二叉堆)实现,其中元素按照优先级顺序组织。在插入新元素时,堆会重新调整以维持优先级顺序。

2.3.1 数据结构

image.png

图说明:
  • PriorityQueue
    • 表示 PriorityQueue 类的实例,是一个基于堆的优先队列。
  • Heap
    • 表示优先队列底层的堆结构,通常是一个二叉堆。
  • Array
    • 堆通常使用数组来存储元素,以实现快速的访问和调整操作。
  • 元素1, 元素2, 元素n
    • 表示数组中存储的具体元素,它们根据优先级顺序被组织在堆中。
  • Comparator
    • 优先队列可以使用一个 Comparator 来确定元素的优先级顺序。

2.3.2 执行流程

image.png

图说明:
  • 开始:执行操作的起始点。
  • 创建 PriorityQueue 实例:初始化 PriorityQueue 对象。
  • 插入元素(offer) :执行将元素添加到优先队列的操作。
  • 构建堆:新元素被添加到堆中,可能需要构建一个新的堆结构。
  • 调整堆:根据优先级调整堆,确保父节点的优先级高于子节点(或反之,取决于是否是最小堆或最大堆)。
  • 结束插入:完成元素的插入操作。
  • 删除元素(poll) :执行将优先级最高的元素从优先队列中移除的操作。
  • 移除顶部元素:移除堆顶部的元素,即优先级最高的元素。
  • 重新调整堆:移除顶部元素后,重新调整堆结构以维持优先级顺序。
  • 结束删除:完成元素的删除操作。
  • 查找顶部元素(peek) :查看但不移除优先队列中优先级最高的元素。
  • 读取顶部元素:读取堆顶部的元素,即优先级最高的元素。
  • 结束查找:完成查找操作。
  • 结束:所有操作完成。

优点

  1. 高效的插入和删除操作
    • 提供了对数时间复杂度的插入和删除操作,这是因为 PriorityQueue 基于堆数据结构实现。
  2. 自动优先级排序
    • 无需手动维护元素的排序,PriorityQueue 会自动根据优先级对元素进行排序。
  3. 灵活性
    • 可以指定自己的 Comparator 来定义元素的优先级顺序,提供了灵活的比较策略。

缺点

  1. 随机访问性能差
    • 由于基于堆实现,随机访问元素的时间复杂度为 O(log n),不如数组或链表结构高效。
  2. 内存开销
    • 相比于简单的列表结构,基于堆的实现可能会有更高的内存开销。
  3. 数据实时性
    • 在多线程环境下,虽然 PriorityQueue 不是线程安全的,但可以通过外部同步机制来保证线程安全,这可能会影响数据的实时性。

使用场景

  • 适用于需要根据优先级处理元素的场景,如任务调度系统。
  • 在资源分配问题中,其中资源请求可以根据优先级进行处理。

8、类设计

image.png

9、应用案例

PriorityQueue 通常用于需要根据任务的紧急程度或重要性来处理任务的情况。这个案例是一个网络代理程序,它使用 PriorityQueue 来根据数据包的优先级顺序处理网络数据包:

import java.util.PriorityQueue;

// 定义一个 IP 数据包类,实现 Comparable 接口以提供自然排序
class IpPacket implements Comparable<IpPacket> {
    private int priority;
    private byte[] data;

    public IpPacket(int priority, byte[] data) {
        this.priority = priority;
        this.data = data;
    }

    public byte[] getData() {
        return data;
    }

    @Override
    public int compareTo(IpPacket o) {
        return Integer.compare(this.priority, o.priority);
    }
}

public class MyNetworkProxy {
    private PriorityQueue<IpPacket> queue;

    public MyNetworkProxy() {
        queue = new PriorityQueue<>();
    }

    // 发送数据包到代理队列
    public void send(IpPacket packet) {
        queue.offer(packet);
    }

    // 从代理队列接收数据包
    public void receive() {
        while (!queue.isEmpty()) {
            IpPacket packet = queue.poll();
            // 处理数据包,例如转发到目标地址
            System.out.println("Received packet with priority: " + packet.priority);
            // 这里可以添加实际的数据处理逻辑
        }
    }

    public static void main(String[] args) {
        MyNetworkProxy proxy = new MyNetworkProxy();
        proxy.send(new IpPacket(1, new byte[] { 10, 20 }));
        proxy.send(new IpPacket(3, new byte[] { 30, 40 }));
        proxy.send(new IpPacket(2, new byte[] { 50, 60 }));

        proxy.receive();
    }
}