优先级队列PriorityQueue

377 阅读2分钟

1 简述

PriorityQueue是Java的数据结构中优先级队列,非线程安全。
理论上最大容量为Integer.MAX_VALUE - 8,但是添加的太多,可能会报OutOfMemoryError异常。

2 源码分析

2.1 构造

private static final int DEFAULT_INITIAL_CAPACITY = 11;

transient Object[] queue; // non-private to simplify nested class access

数据用数组的形式存储,默认初始大小为11。

2.2 offer

add同样走的offer方法。

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

public boolean offer(E e) {
    // 添加null会抛空指针异常
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)
        // 扩容一倍或者50%
        grow(i + 1);
    size = i + 1;
    // 没有元素,放第一个
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}

// 扩容
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}

// 放元素,并保证逻辑上为完全二叉树
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}

整个添加元素的过程,就是先判断是否为null,是否需要扩容,然后插入元素到数组对应位置,保证逻辑上为完全二叉树。

2.3 siftUpUsingComparator

private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

该方法首先获取最后一个位置的父节点的索引,然后定义变量接受父节点的值,如果新增的节点值和父节点的值比较之后满足堆结构,则直接break返回,否则循环向上交换,最终完成堆结构调整。

2.4 peek

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

总是取第一个元素,也就是顶堆最上面的父节点。

2.5 remove

public boolean remove(Object o) {
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
        removeAt(i);
        return true;
    }
}

private E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    if (s == i) // removed last element
        queue[i] = null;
    else {
        E moved = (E) queue[s];
        queue[s] = null;
        siftDown(i, moved);
        if (queue[i] == moved) {
            siftUp(i, moved);
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}

如果是最后一个结点,直接置为null就行。
如果不是,会取出最后一个结点缓存在moved,然后删除最后一个结点,将最后一个结点的值覆盖在待删除结点位置,完成删除操作。
最后调整数组结构,使其符合完全二叉树!
pollremove的逻辑基本一致。

总结

PriorityQueue本质还是一个FIFO的队列,其特殊之处在于他的出队顺序是按照优先级进行比较的。
至于这个优先级如何确定,需要结点去实现Comparator接口,提供比较大小的逻辑。