【Java】PriorityQueue详解:从使用到底层原理全解析

大家好,我是云扬~ 今天想和大家深入聊聊 Java 中的 PriorityQueue,这个基于优先级堆的优先队列实现,在日常开发和算法场景中都特别实用。它最特别的地方在于打破了普通队列 “先进先出” 的规则,能按照元素优先级自动排序,插入和删除操作的时间复杂度仅为 O (log n),效率很高。

一、PriorityQueue 简介

PriorityQueue 是 Java 集合框架中的重要成员,底层基于优先级堆实现,核心作用是维护元素的优先级顺序。当我们插入元素时,它会自动将元素放到合适的位置;取出元素时,始终是优先级最高的元素先出队。这一特性让它在任务调度、事件处理、算法实现(比如 Dijkstra、Prim 算法)等场景中大放异彩。

二、PriorityQueue 实战使用

1. 基础使用流程

首先我们来看最基本的创建、添加、遍历操作,代码示例如下:

import java.util.PriorityQueue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 1. 创建PriorityQueue对象(默认小顶堆,自然排序)
        PriorityQueue<String> priorityQueue = new PriorityQueue<>();

        // 2. 添加元素(offer()方法,插入失败返回false)
        priorityQueue.offer("MySQL");
        priorityQueue.offer("云扬");
        priorityQueue.offer("Java编程");

        // 3. 遍历并打印元素(poll()方法取出并删除队首元素)
        System.out.println("默认优先级遍历结果:");
        while (!priorityQueue.isEmpty()) {
            System.out.print(priorityQueue.poll() + " ");
        }
        // 输出结果:Java编程 MySQL 云扬(按字符串自然排序)
    }
}

2. 指定优先级顺序

默认情况下,PriorityQueue 是小顶堆(自然排序),如果我们需要大顶堆或者自定义排序规则,可以通过传入Comparator实现:

import java.util.Comparator;
import java.util.PriorityQueue;

public class PriorityQueueCustomDemo {
    public static void main(String[] args) {
        // 创建大顶堆(反转自然排序)
        PriorityQueue<String> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());

        maxHeap.offer("MySQL");
        maxHeap.offer("云扬");
        maxHeap.offer("Java编程");

        System.out.println("大顶堆遍历结果:");
        while (!maxHeap.isEmpty()) {
            System.out.print(maxHeap.poll() + " ");
        }
        // 输出结果:云扬 MySQL Java编程 (按字符串反转排序)

        // 自定义对象排序(以User的age为例)
        PriorityQueue<User> userHeap = new PriorityQueue<>(Comparator.comparingInt(User::getAge));
        userHeap.offer(new User("1","张三", 25));
        userHeap.offer(new User("2","李四", 20));
        userHeap.offer(new User("3","王五", 30));

        System.out.println("\n自定义对象排序结果:");
        while (!userHeap.isEmpty()) {
            User user = userHeap.poll();
            System.out.print(user.getName() + "(" + user.getAge() + ") ");
        }
        // 输出结果:李四(20) 张三(25) 王五(30)
    }
    
    static class User {
        private String id;
        private String name;
        private int age;
        
        public User(String id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
        
        public String getId() { return id; }
        public String getName() { return name; }
        public int getAge() { return age; }
    }
}

三、底层实现原理

1. 堆结构基础

PriorityQueue 的底层是完全二叉树实现的堆,默认是小顶堆(每个节点的值≤其子节点的值)。堆的存储非常高效,直接使用数组即可,无需额外指针:

  • 父节点下标:(i - 1) / 2(i 为当前节点下标)
  • 左子节点下标:2 * i + 1
  • 右子节点下标:2 * i + 2

比如数组[a, b, c, d, e]对应的堆结构:

     a (0)    // 根节点
    / \
  b(1) c(2)   // 第二层
  / \
d(3) e(4)     // 第三层

2. 核心方法剖析

PriorityQueue 的核心方法都围绕堆的调整(上浮 siftUp、下沉 siftDown)展开,我们逐一解析:

(1)添加元素:add () vs offer ()

两者语义一致,都是插入元素,区别仅在于插入失败时的处理:

  • add(E e):插入失败抛出IllegalStateException
  • offer(E e):插入失败返回false(更推荐使用)

底层实现逻辑:先扩容(如果数组已满),再将元素插入数组末尾,最后通过siftUp调整堆结构,保证堆的性质。关键源码片段(简化):

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)
        grow(i + 1); // 扩容
    size = i + 1;
    if (i == 0)
        queue[0] = e; // 空堆直接插入根节点
    else
        siftUp(i, e); // 上浮调整
    return true;
}

(2)获取元素:element () vs peek ()

两者都用于获取队首元素(不删除),区别在于队列为空时的处理:

  • element():抛出NoSuchElementException
  • peek():返回null(更推荐使用)

底层直接返回数组 0 下标处的元素(堆顶):

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

(3)删除元素:remove () vs poll ()

两者都用于获取并删除队首元素,区别在于队列为空时的处理:

  • remove():抛出NoSuchElementException
  • poll():返回null(更推荐使用)

底层逻辑:取出堆顶元素,将数组末尾元素移到堆顶,再通过siftDown调整堆结构:

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0]; // 堆顶元素
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x); // 下沉调整
    return result;
}

(4)删除指定元素:remove (Object o)

用于删除队列中与指定对象相等的元素,删除后需要调整堆结构,时间复杂度为 O (n)(需先遍历找到元素):

public boolean remove(Object o) {
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
        removeAt(i); // 调整堆结构
        return true;
    }
}

四、总结

PriorityQueue 是 Java 中处理优先级元素的利器,核心要点总结如下:

  1. 底层基于堆(完全二叉树)实现,使用数组存储,效率高;
  2. 默认小顶堆,支持通过 Comparator 自定义排序规则;
  3. 插入 / 删除元素时间复杂度 O (log n),查询堆顶元素 O (1);
  4. 常用方法推荐使用offer()peek()poll(),避免抛出异常;
  5. 适用于任务调度、算法实现等需要按优先级处理元素的场景。

如果需要使用 PriorityQueue 存储自定义对象,记得让对象实现Comparable接口,或者在创建队列时传入Comparator,否则会抛出ClassCastException哦~ 大家在实际开发中有没有用过 PriorityQueue?欢迎在评论区分享你的使用场景!

Tags:

发表回复

Your email address will not be published. Required fields are marked *.

*
*