JDK 8 PriorityQueue 详解及详细源码展示

JDK 8 中的 PriorityQueue 是基于二叉堆(最小堆,默认)实现的优先队列,它支持高效的按优先级插入和删除元素操作(时间复杂度为 O(log n) 平均时间)。与普通队列(FIFO)不同,PriorityQueue 的出队顺序由元素的优先级决定(自然顺序或自定义比较器),适用于需要“按优先级处理”的场景(如任务调度、事件优先级队列)。

一、PriorityQueue 的核心特性

特性说明
优先队列元素按自然顺序(若实现 Comparable)或自定义 Comparator 排序,默认最小堆。
无界队列底层数组动态扩容,理论上容量无上限(受内存限制)。
不保证迭代顺序迭代器遍历顺序与堆结构无关(仅保证出队顺序按优先级)。
非线程安全多线程环境下需手动同步(如 Collections.synchronizedSet)。
允许 null 元素若使用自然排序(无 Comparator),插入 null 会抛出 NullPointerException;若使用自定义 Comparator,需处理 null

二、PriorityQueue 的核心结构

1. 继承与接口
public class PriorityQueue<E> extends AbstractQueue<E> 
                          implements java.io.Serializable
  • AbstractQueue:提供 Queue 接口的骨架实现(如 addremove 等方法的默认逻辑)。
  • Serializable:支持序列化。
2. 核心属性

PriorityQueue 的底层通过动态数组存储堆结构,核心属性如下:

// 存储堆元素的数组(初始容量为 11,或通过构造方法指定)
transient Object[] queue;

// 元素数量
private int size = 0;

// 比较器(若为 null,使用元素的自然排序)
private final Comparator<? super E> comparator;

// 序列化版本号
private static final long serialVersionUID = -7720805057307804929L;
3. 堆的性质

PriorityQueue 使用二叉堆(完全二叉树)存储元素,通过数组实现:

  • 父节点与子节点索引关系
    • 父节点索引:(i - 1) >>> 1(等价于 (i - 1) / 2,无符号右移避免负数)。
    • 左子节点索引:(i << 1) + 1(等价于 2 * i + 1)。
    • 右子节点索引:(i << 1) + 2(等价于 2 * i + 2)。
  • 堆特性:父节点的优先级总是高于(或等于)子节点(最小堆);若使用自定义 Comparator,则按比较器规则调整。

三、构造方法

PriorityQueue 提供了多种构造方法,核心是初始化堆数组和比较器:

1. 默认构造方法(自然排序,初始容量 11)
public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

private static final int DEFAULT_INITIAL_CAPACITY = 11;

public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
    if (initialCapacity < 1) {
        throw new IllegalArgumentException();
    }
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}
2. 指定初始容量的构造方法
public PriorityQueue(int initialCapacity) {
    this(initialCapacity, null);
}
3. 从集合初始化(按自然排序或比较器)
public PriorityQueue(Collection<? extends E> c) {
    this(comparator = null); // 默认自然排序
    addAll(c); // 添加所有元素并调整堆
}

public PriorityQueue(SortedSet<? extends E> s) {
    this(s.comparator()); // 继承 SortedSet 的比较器
    addAll(s);
}
4. 从迭代器初始化(按自然排序)
public PriorityQueue(Iterator<? extends E> c) {
    this(); // 默认自然排序
    while (c.hasNext()) {
        add(c.next());
    }
}

四、核心方法解析

PriorityQueue 的核心操作围绕堆的插入、删除和调整展开,以下是最常用方法的源码解析:

1. add(E e) / offer(E e):添加元素

addoffer 功能相同(PriorityQueue 是无界队列,offer 始终返回 true),核心逻辑是将元素插入堆末尾,然后通过**上浮(siftUp)**调整堆结构。

public boolean add(E e) {
    return offer(e);
}

public boolean offer(E e) {
    if (e == null) {
        throw new NullPointerException(); // 不允许 null 元素(自然排序时)
    }
    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;
}

// 上浮调整(从索引 i 开始,与父节点比较)
private void siftUp(int k, E x) {
    if (comparator != null) {
        // 使用自定义比较器
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0) { // x 优先级不高于父节点,停止调整
                break;
            }
            queue[k] = e; // 父节点下移
            k = parent;
        }
    } else {
        // 使用自然排序(元素必须实现 Comparable)
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0) { // x 优先级不高于父节点,停止调整
                break;
            }
            queue[k] = e; // 父节点下移
            k = parent;
        }
    }
    queue[k] = x; // 插入最终位置
}
2. poll():移除并返回队首元素(最小/最大)

poll 是核心方法,用于取出堆顶元素(优先级最高的元素)。若堆为空,返回 null。核心逻辑是:

  1. 取出堆顶元素(queue[0])。
  2. 将堆末尾元素移到堆顶(queue[0] = queue[size-1])。
  3. 通过**下沉(siftDown)**调整堆结构。
public E poll() {
    if (size == 0) {
        return null;
    }
    int s = --size;
    E result = (E) queue[0];
    E x = (E) queue[s];
    queue[s] = null; // 清除末尾元素引用(GC 友好)
    if (s != 0) { // 堆非空,下沉调整
        siftDown(0, x);
    }
    return result;
}

// 下沉调整(从索引 i 开始,与子节点比较)
private void siftDown(int k, E x) {
    int half = size >>> 1; // 堆的最后一个非叶子节点索引(half = size/2 - 1)
    while (k < half) {
        int left = (k << 1) + 1; // 左子节点索引
        int right = left + 1;    // 右子节点索引
        int smallest = left;     // 假设左子节点最小

        // 比较左、右子节点,找到最小的
        if (right < size && comparatorCompare(queue[right], queue[left]) < 0) {
            smallest = right;
        }

        // 比较当前节点与最小子节点
        if (comparatorCompare(x, queue[smallest]) <= 0) { // x 优先级不高于最小子节点,停止调整
            break;
        }
        queue[k] = queue[smallest]; // 最小子节点上移
        k = smallest;
    }
    queue[k] = x; // 插入最终位置
}

// 辅助方法:使用比较器比较两个元素
private int comparatorCompare(Object a, Object b) {
    return comparator != null ? comparator.compare((E) a, (E) b) : ((Comparable<? super E>) a).compareTo((E) b);
}
3. peek():查看队首元素(不删除)

peek 方法返回堆顶元素(优先级最高的元素),若堆为空则返回 null

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}
4. remove(Object o):删除指定元素

remove 方法删除堆中指定的元素(若存在),返回 true;否则返回 false。核心逻辑是:

  1. 找到元素的索引(通过遍历或辅助数据结构,但 PriorityQueue 未优化此操作,时间复杂度为 O(n))。
  2. 将该元素与堆末尾元素交换,然后下沉调整堆。
public boolean remove(Object o) {
    int i;
    if ((i = indexOf(o)) != -1) {
        E x = (E) queue[i];
        int s = --size;
        if (s == i) { // 元素在堆顶,直接下沉
            queue[i] = null;
            if (s > 0) {
                siftDown(0, x);
            }
        } else { // 元素在中间或末尾,交换到末尾后下沉
            E moved = (E) queue[s];
            queue[s] = null;
            if (comparator == null) {
                siftDownWithComparator(i, moved, queue, size); // 使用比较器下沉
            } else {
                siftDown(i, moved); // 使用自然排序下沉(可能有问题,需看具体实现)
            }
        }
        return true;
    }
    return false;
}

// 辅助方法:查找元素的索引(线性遍历,O(n) 时间)
private int indexOf(Object o) {
    if (o != null) {
        for (int i = 0; i < size; i++) {
            if (o.equals(queue[i])) {
                return i;
            }
        }
    }
    return -1;
}
5. iterator():返回迭代器

PriorityQueue 的迭代器按堆的底层数组顺序遍历元素(非优先级顺序),迭代过程中若队列被修改(非通过迭代器的 remove),会抛出 ConcurrentModificationException(快速失败机制)。

public Iterator<E> iterator() {
    return new Itr();
}

private final class Itr implements Iterator<E> {
    private int cursor = 0; // 下一个要访问的索引

    public boolean hasNext() {
        return cursor < size;
    }

    public E next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return (E) queue[cursor++];
    }
}

五、堆的扩容机制(grow)

当堆的容量不足时(size >= queue.length),触发扩容:

private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // 新容量为原容量的 2 倍(若原容量 < 64)或原容量 + 原容量/4(若原容量 >= 64)
    int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));
    // 限制最大容量(避免内存溢出)
    if (newCapacity > MAX_ARRAY_SIZE) {
        newCapacity = hugeCapacity(minCapacity);
    }
    queue = Arrays.copyOf(queue, newCapacity);
}

private int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) {
        throw new OutOfMemoryError();
    }
    return (MAX_ARRAY_SIZE >= minCapacity) ? MAX_ARRAY_SIZE : Integer.MAX_VALUE;
}

六、关键源码细节

1. 序列化与反序列化

PriorityQueue 实现了 Serializable 接口,序列化时保存 queue 数组和 comparator;反序列化时重建堆结构:

// 序列化时写入 queue 和 comparator
private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    s.writeInt(size);
    for (int i = 0; i < size; i++) {
        s.writeObject(queue[i]);
    }
}

// 反序列化时读取并重建堆
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    int size = s.readInt();
    queue = new Object[Math.max(size, 11)]; // 至少初始容量 11
    for (int i = 0; i < size; i++) {
        queue[i] = s.readObject();
    }
    this.size = size;
    heapify(); // 重建堆结构
}

// 从数组重建堆(下沉所有非叶子节点)
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--) {
        siftDown(i, (E) queue[i]);
    }
}

七、性能与注意事项

1. 性能特点
  • 插入(add/offer):平均时间复杂度 O(log n)(堆调整的上浮操作)。
  • 删除(poll/remove):平均时间复杂度 O(log n)(堆调整的下沉操作)。
  • 查找队首(peek):时间复杂度 O(1)(直接访问堆顶)。
  • 迭代(iterator):时间复杂度 O(n)(遍历数组),但顺序不保证。
2. 注意事项
  • 非线程安全:多线程环境下同时修改 PriorityQueue 可能导致数据不一致(如 ConcurrentModificationException)。解决方案:
    Queue<E> synchronizedQueue = Collections.synchronizedQueue(new PriorityQueue<>());
    
  • 迭代顺序不确定:迭代器遍历的是堆的底层数组,与优先级顺序无关。若需按优先级遍历,需通过 poll() 逐个取出元素。
  • null 元素限制:若使用自然排序(无 Comparator),插入 null 会抛出 NullPointerException;若使用自定义 Comparator,需确保比较器能处理 null
  • 扩容开销:扩容时需要复制数组,频繁插入可能导致性能下降(可通过预分配足够容量优化)。

八、总结

PriorityQueue 是基于二叉堆的高效优先队列,核心优势是按优先级插入和删除元素O(log n) 时间复杂度)。其内部通过动态数组存储堆结构,通过上浮和下沉操作维护堆的性质。实际开发中,若需要按优先级处理任务(如事件调度、资源分配),PriorityQueue 是首选;若需频繁迭代或保证迭代顺序,需谨慎使用(或改用其他数据结构)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值