掘金 后端 ( ) • 2024-07-02 23:47

不少刷过题的小伙伴多少应该听过PriorityQueue(优先队列)这个数据结构,而Java中的PriorityQueue(优先队列)本质其实就是个,它的每个元素都根据比较器进行比较后赋予了一个优先级,优先级最高(默认是数值最小的,及俗称的“小根堆”)的元素位于队列的头部(出队列的话优先级高的会先出)。

案例演示

我们举个例子吧

我们需要一个主函数,代码如下:

public static void main(String[] args) {
    PriorityQueue<Employee> queue = new PriorityQueue<Employee>();

    queue.add(new Employee("Lisi", 18));
    queue.add(new Employee("Zhangsan", 99));
    queue.add(new Employee("Wangwu", 22));

    while (!queue.isEmpty()) {
        Employee poll = queue.poll();
        System.out.println("name: " + poll.getName() + " age: " + poll.getAge());
    }
}

自定义一个Employee类,代码如下:

class Employee implements Comparable<Employee> {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee o) {
        return this.getAge() - o.getAge();
    }
}

上述例子的输出结果如下:

在这个示例中,我们首先创建了一个PriorityQueue,并添加了一些Employee对象,然后通过循环不断将队列内的元素弹出,并输出弹出元素的nameage,由于在自定义类Employee中,我们使用年龄作为排序的依据(即重写了compareTo()方法)

由于比较的是int类型的数,所以我们可以查看他相关的实现类

从如下源码中不难看出,真正决定元素顺序的逻辑是(x < y) ? -1 : ((x == y) ? 0 : 1)

即如同函数上面介绍里说的

the value 0 if this Integer is equal to the argument Integer; a value less than 0 if this Integer is numerically less than the argument Integer; and a value greater than 0 if this Integer is numerically greater than the argument Integer (signed comparison).

简单翻译下就是:

  • 返回0:第一个数 = 第二个数
  • 返回负数:第一个数 < 第二个数
  • 返回正数:第一个数 > 第二个数

值得注意的是:不能添加null元素,否则会抛出NullPointerException异常(如下图)

源码分析

创建

Java中的PriorityQueue的底层是一个Object数组,初始化时,如果执行的是无参构造,默认大小11

添加

在向PriorityQueue中添加元素时,如果队列是空,添加的元素自然就是队首,否则会调整元素的位置以满足堆的性质,如果发现Queue的容量大于或等于队列的长度,就会自动调用grow()方法进行扩容

调用siftUp()函数调整树结构

会根据用户是否传入比较器来进行分别处理,如下图红色框框为传入比较器的情况,黄色框框为未传入比较器的情况

扩容

每次扩容会根据旧容量进行判断,如果旧容量小于64,则新容量在原长度加上2(oldCapacity + 2),如果旧容量大于64,则新容量变为原来的2倍(oldCapacity >> 1)。

其中*MAX_ARRAY_SIZE* = Integer.MAX_VALUE - 8为什么是减8?这是一个避免过界的经验值

删除

删除操作按最小元素优先的规则,每次弹出最小元素,如果被删除的元素不是唯一的元素,则需要进行调整位置的操作,保持小根堆的性质。

总结

总结一下:PriorityQueue在物理结构上是一个数组,但逻辑结构上是堆,会以特定的优先级进行排序,优先级高的数据会优先被弹出,如果PriorityQueue满了,会自动进行扩容。

特点

  1. 基于优先级堆实现,元素按照一定的优先级顺序排列
  2. 可以通过自然顺序(元素自身的比较规则)或自定义的 Comparator 来确定元素的优先级
  3. 无界队列,没有固定的容量限制,可以根据需要自动扩展

其实这些特点总结起来就是→可以自定义设置优先级/排序,基于他的这个特点,PriorityQueue有以下适用场景:

  1. 任务调度:可以根据任务的优先级来安排执行顺序(可自定义排序)
  2. 事件处理:按照事件的重要性或紧急程度进行处理
  3. 资源分配:根据资源请求的优先级进行分配(可自定义优先级)
  4. 数据排序和筛选:可以快速获取优先级最高的元素,适用于需要逐步处理具有优先级的数据的情况