PriorityQueue源码分析

405 阅读4分钟

简介

PriorityQueue(优先级队列)的数据结构是二叉堆。准确的说是一个小顶堆。

二叉堆是一个特殊的堆, 它近似完全二叉树。
当父节点的键值总是大于或等于任何一个子节点的键值时为大顶堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为小顶堆。

PriorityQueue不是线程安全的, 类似的PriorityBlockingQueue是线程安全的。

源码分析

主要属性

private static final int DEFAULT_INITIAL_CAPACITY = 11; // 默认数组容量

transient Object[] queue; // 数组

private int size = 0; // 当前数组容量

private final Comparator<? super E> comparator; // 比较器

transient int modCount = 0; // 修改次数

主要方法

offer(E e)

add 内部也是调用的此方法

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++; // 修改次数+1
    int i = size; // 数组当前容量
    if (i >= queue.length) // 如果容量大于队列长度
        grow(i + 1); // 扩容
    size = i + 1; // 容量+1
    if (i == 0) // 首次添加
        queue[0] = e;
    else
        siftUp(i, e); // 上浮
    return true;
}

private void siftUp(int k, E x) {
    if (comparator != null) // comparator 不为null
        siftUpUsingComparator(k, x);
    else // 如果为null,就用元素的自然排序
        siftUpComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
    // k 数组元素下标
    // x 元素
    // 如果元素没有实现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) // 如果待插入元素大于父类元素,直接退出
            break;
        queue[k] = e; // 和父类交换位置,保证父类元素小于子类元素(小顶堆)
        k = parent;
    }
    queue[k] = key; // 调整结束,放入k位置
}

(1)入队不允许null元素;

(2)如果数组不够用了,先扩容;

(3)如果还没有元素,就插入下标0的位置;

(4)如果有元素了,就插入到最后一个元素往后的一个位置(实际并没有插入哈);

(5)自下而上堆化,一直往上跟父节点比较;

(6)如果比父节点小,就与父节点交换位置,直到出现比父节点大为止;

poll()

 public E poll() {
    if (size == 0) // 数组没有元素
        return null;
    int s = --size; // 数组最后一个元素的下标
    modCount++; // 修改次数+1
    E result = (E) queue[0]; // 因为是小顶堆,每次poll都是返回数组的第一个元素
    E x = (E) queue[s]; // 拿到数组的最后一个元素
    queue[s] = null; // 最后位置赋值为null
    if (s != 0) // 容量多于一个
        siftDown(0, x); // 下沉操作,把x放到下标为0的位置,和子类比较,如果大于子类,执行下沉操作,直到找到合适的位置
    return result;
}

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else // 自然排序
        siftDownComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
    // k 数组元素下标
    // x 元素
    // 如果元素没有实现Comparable接口,此处会出现类型转换异常
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // 只比较数组容量的一半(叶子节点就占用了一半元素)
    while (k < half) {
        /**
         * 2*n+1 左子节点
         * 2*(n+1) 右子节点
         */
        int child = (k << 1) + 1; // 左子节点下标
        Object c = queue[child]; // 左子节点元素,认为左子元素是最小的
        int right = child + 1; // 右子节点下标
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) // 若左子元素大于右子元素
            c = queue[child = right]; // 拿到右子元素
        if (key.compareTo((E) c) <= 0) // 比子元素最小的元素还小,就不需要再调整了
            break;
        queue[k] = c; // 比最小的子元素大,调整位置
        k = child;
    }
    queue[k] = key; // 放入调整之后的位置
}

(1)将队列首元素弹出;

(2)将队列末元素移到队列首;

(3)自上而下堆化,一直往下与最小的子节点比较;

(4)如果比最小的子节点大,就交换位置,再继续与最小的子节点比较;

(5)如果比最小的子节点小,就不用交换位置了,堆化结束;

(6)这就是堆中的删除堆顶元素;

grow(int minCapacity)

private void grow(int minCapacity) {
    int oldCapacity = queue.length; // 当前数组容量
    // Double size if small; else grow by 50%
    /*
        如果当前数组容量小于64,扩容容量就翻倍,反之就扩容之前的一半
     */
    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 static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // 容量溢出
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

(1)当数组比较小(小于64)的时候每次扩容容量翻倍;

(2)当数组比较大的时候每次扩容只增加一半的容量;

remove(Object o)

public boolean remove(Object o) {
    int i = indexOf(o); // 找到元素o在数组中的下标
    if (i == -1) // 数组中没有此元素
        return false;
    else {
        removeAt(i); // 删除元素
        return true;
    }
}

private E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++; // 修改次数+1
    int s = --size; // 最后一个元素的下标
    if (s == i) // 如果删除的是最后一个元素
        queue[i] = null;
    else { // 删除的不是最后一个
        E moved = (E) queue[s]; // 拿到最后一个元素
        queue[s] = null; // 最后一个元素赋值null
        siftDown(i, moved); // 先执行下沉操作,如果能够向下调整,就无须上浮操作了
        if (queue[i] == moved) { // 没有向下调整
            siftUp(i, moved); // 上浮操作
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}