JDK源码PriorityQueue源码分析

Posted LL.LEBRON

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK源码PriorityQueue源码分析相关的知识,希望对你有一定的参考价值。

PriorityQueue源码分析

1.简介

优先级队列,是0个或多个元素的集合,集合中的每个元素都有一个权重值,每次出队都弹出优先级最大或最小的元素。

一般来说,优先级队列使用堆来实现。

2.主要属性

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

// 存储元素的数组
transient Object[] queue; // non-private to simplify nested class access

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

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

// 修改次数
transient int modCount = 0; // non-private to simplify nested class access

从源码可以看出:

  1. 默认容量是11
  2. queue:元素存储在数组中,这跟我们之前说的堆一般使用数组来存储是一致的
  3. comparator:比较器,在优先级队列中,也有两种方式比较元素,一种是元素的自然顺序,一种是通过比较器来比较
  4. modCount:修改次数,有这个属性表示PriorityQueue也是fast-fail

3.入队

//入队
public boolean add(E e) 
    //调用的offer(E e)
    return offer(e);


//入队
public boolean offer(E e) 
    //不支持null元素,否则抛出异常
    if (e == null)
        throw new NullPointerException();
    //修改次数加一
    modCount++;
    //取size
    int i = size;
    //元素个数达到最大容量,扩容
    if (i >= queue.length)
        grow(i + 1);
    //元素个数加一
    size = i + 1;
    //如果当前队列还没有元素,直接插入到数组的第一个位置
    if (i == 0)
        queue[0] = e;
    else
        //否则,插入元素到数组的size位置,也就是最后一个元素的下一位
        //这里size不是数组大小,而是元素个数
        //然后做自下而上的堆化
        siftUp(i, e);
    return true;

private void siftUp(int k, E x) 
    //根据是否有比较器使用不同的方法
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);


@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) 
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) 
        //找到父节点的位置
        //因为元素从0开始,所以减一后在除2
        int parent = (k - 1) >>> 1;
        //父节点的值
        Object e = queue[parent];
        //比较插入的元素与父节点的值
        //如果比父节点大,则跳出循环
        //否则交换位置
        if (key.compareTo((E) e) >= 0)
            break;
        //与父节点交换位置
        queue[k] = e;
        //现在插入的元素位置移到了父节点的位置
        //继续与父节点再比较
        k = parent;
    
    //最后找到应该插入的位置,放入元素
    queue[k] = key;

整个流程:

  1. 入队不允许null元素
  2. 如果数组不够用了,先扩容
  3. 如果还没有元素,就插入下标0的位置
  4. 如果有元素了,就插入到最后一个元素往后的一个位置(实际并没有插入哈)
  5. 自下而上堆化,一直往上跟父节点比较
  6. 如果比父节点小,就与父节点交换位置,直到出现比父节点大为止
  7. 由此可见,PriorityQueue是一个小顶堆

4.扩容

private void grow(int minCapacity) 
    //旧容量
    int oldCapacity = queue.length;
    // 旧容量小于64时,容量翻倍
    // 旧容量大于等于64,容量只增加旧容量的一半
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
            (oldCapacity + 2) :
            (oldCapacity >> 1));
    // 检查是否溢出
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 创建出一个新容量大小的新数组并把旧数组元素拷贝过去
    queue = Arrays.copyOf(queue, newCapacity);


private static int hugeCapacity(int minCapacity) 
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;

  1. 当数组比较小**(小于64)的时候每次扩容容量翻倍**
  2. 当数组比较大**(大于等于64)的时候每次扩容只增加一半的容量**

5.出队

@SuppressWarnings("unchecked")
public E poll() 
    // 如果size为0,说明没有元素
    if (size == 0)
        return null;
    // 弹出元素,元素个数减1
    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;

//自上而下的堆化
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) 
    Comparable<? super E> key = (Comparable<? super E>) x;

    // 只需要比较一半就行了,因为叶子节点占了一半的元素
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) 
        // 寻找子节点的位置,这里加1是因为元素从0号位置开始
        int child = (k << 1) + 1; // assume left child is least
        //左子节点的值
        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.取队首元素

public E peek() 
    //队列为空返回null
    //否则返回堆顶元素也就是queue[0]
    return (size == 0) ? null : (E) queue[0];

  1. 如果有元素就取下标0的元素
  2. 如果没有元素就返回null

7.总结

  1. PriorityQueue是一个小根堆
  2. PriorityQueue是非线程安全的
  3. PriorityQueue不是有序的只有堆顶存储着最小的元素
  4. 入队就是堆的插入元素的实现
  5. 出队就是堆的删除元素的实现

以上是关于JDK源码PriorityQueue源码分析的主要内容,如果未能解决你的问题,请参考以下文章

PriorityQueue源码解析

JDK 1.8 源码解析 PriorityQueue

PriorityQueue 源码分析

PriorityQueue源码分析

PriorityQueue原理分析——基于源码

死磕 java集合之PriorityQueue源码分析