JDK源码解析之Queue与其实现类PriorityQueue

前言:

    前文介绍了Stack这种数据结构类型,它符合后进先出(LIFO)的操作顺序。

    今天介绍与其相反操作顺序的一种数据结构,Queue(队列),它符合先进先出(FIFO)的操作顺序

    从网络上截一个图(如有侵权,请联系作者),简单表示下队列结构

    a1是最先进入队列的元素,现在排在队头,an是最后入队的元素,排在队尾

    执行出队操作的时候,最先进入队列的a1元素,最先出队

1.Queue

    java.util.Queue是一个接口,实现类有以下

/** 
 * @see java.util.Collection
 * @see LinkedList
 * @see PriorityQueue
 * @see java.util.concurrent.LinkedBlockingQueue
 * @see java.util.concurrent.BlockingQueue
 * @see java.util.concurrent.ArrayBlockingQueue
 * @see java.util.concurrent.LinkedBlockingQueue
 * @see java.util.concurrent.PriorityBlockingQueue
 * @since 1.5
 * @author Doug Lea
 * @param <E> the type of elements held in this collection
 */
public interface Queue<E> extends Collection<E> {

    之前在介绍LinkedList的时候,也能看到其实现了Deque接口,Deque继承了Queue,所以LinkedList实现了Queue接口。

    今天介绍下另一种Queue的实现类,PriorityQueue

2.PriorityQueue数据结构分析

    关于PriorityQueue的数据结构,就是堆。

    关于堆的内容笔者不再详述,可以参考:小灰漫画 什么是二叉堆

    笔者之前也有写过类似的博客:https://blog.csdn.net/qq_26323323/article/details/79708103 

    借用小灰漫画的图就是:

    二叉堆本质上是一个完全二叉树,它分为两个类型:最大堆和最小堆

    最大堆就是:每一个父节点的值,都大于等于它左右子节点的值,如下所示

    最小堆就是:每一个父节点的值,都小于等于它左右子节点的值,如下所示:

    依据这种堆结构,在堆顶的元素要么是最大的元素,要么是最小的元素,可以快速查找最大最小值

3.PriorityQueue结构分析

/**
The elements of the priority queue are ordered according to their
 * {@linkplain Comparable natural ordering}, or by a {@link Comparator}
*/
public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    
    // 默认容量是11
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    
    // 使用数组来存储元素
    transient Object[] queue;
    
    // 与ArrayList类似,也有一个size属性
    private int size = 0;
    
    // 如何实现优先级的队列呢,主要就是靠这个比较器
    private final Comparator<? super E> comparator;

    总结:可以看出,其与ArrayList的成员变量差不多,主要区别就是一个比较器comparator

4.构造方法

// 1.空参构造,默认容量是11
public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

// 2.给定初始容量
public PriorityQueue(int initialCapacity) {
    this(initialCapacity, null);
}

// 3.给定比较器
public PriorityQueue(Comparator<? super E> comparator) {
    this(DEFAULT_INITIAL_CAPACITY, comparator);
}

// 4.同时给定初始容量和比较器
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) {
    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

    构造方法主要是围绕初始容量和比较器来创建的

5.添加操作

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

// offer()
public boolean offer(E e) {}

    可以看到,add方法直接调用了offer方法,那么我们直接看下offer方法的实现即可

// PriorityQueue.offer()
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    // 与ArrayList类似,在添加之前需要先看是否需要扩容
    // size是queue里真正有数据的大小
    // 而queue.length初始是11,是大于size的
    // 当size>=queue.length时,需要进行扩容操作
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;
    if (i == 0)
        queue[0] = e;
    else
        // 主要看这里,是如何实现优先级的存放的
        siftUp(i, e);
    return true;
}

// 扩容操作
// 当前队列长度小于64,则扩容两倍;否则扩容1.5倍
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);
}
// PriorityQueue.siftUp()
private void siftUp(int k, E x) {
    // 如果指定了比较器,则使用指定的,否则使用默认比较器
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}

// PriorityQueue.siftUpUsingComparator()
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        // 1.获取父节点,也就是(k-1)/2位置处的值
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        // 2.如果当前节点大于父节点,则将k位置赋值为x
        // 否则将k位置parent位置处的值e
        // 总体来说实现的是一个最小堆,父节点值小于其左右子节点
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

// PriorityQueue.siftUpComparable()
// 与siftUpUsingComparator方法类似,只是用了对象本身的比较器而已 
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
    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;
}

    总结:添加方法主要就是为了实现一个最小堆,将最小的元素往上移动

6.删除操作

// PriorityQueue.poll()
public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    // 1.获取堆顶元素
    E result = (E) queue[0];
    // 2.获取最后一个元素,在整个堆中比较大
    E x = (E) queue[s];
    queue[s] = null;
    
    // s=0说明,当前元素已经是最后一个元素了,
    // s!=0时,需要将x置为堆顶,然后做下沉操作
    if (s != 0)
        siftDown(0, x);
    return result;
}

// PriorityQueue.siftDown()
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

// PriorityQueue.siftDownUsingComparator()
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    // k=1,为堆顶元素,执行下沉操作
    // 比较其左子节点和右子节点,
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        // 一直比较到当前节点小于某一节点为止
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

    被删除的节点就是堆顶元素,也就是当前最小的元素,删除之后需要补充一个元素,一般来说就是补充最后一个元素到堆顶,这个元素肯定是比较大的元素,不符合最小堆的规范,则需要将这个元素逐渐下沉,下沉到合适的位置即可

7.查询操作

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

    查询只有一个获取堆顶元素的方法,获取的就是整个堆中最大或最小的值

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/86082073
今日推荐