PriorityQueue And Queue


title: PriorityQueue and Queue source code analysis
date: 2018-3-3 23:18:40
categories:
- JDK
tags:
- JDK

- Source code learning

此博文过长,纯属自己记录的笔记,慎入。

ArrayDeque

It is a two-way queue, and both ports of the queue can be enqueued and dequeued. Going a step further, in fact, ArrayDeque can be said to be a two-way circular queue

defination

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
{

}

As can be seen from the definition of ArrayDeque, it inherits AbstractCollection and implements Deque, Cloneable, Serializable interfaces. I don't know if you will find anything here. We have seen the Deque interface in LinkedList, and LinkedList also implements the Deque interface. Deque is a double-ended queue, which is implemented in the Queue interface, that is, it can be enqueued and dequeued at the same end of the queue, so Deque can be used as both a queue and a stack.
To know the Dequeue Prophet Queue, the source code of jdk1.8 is as follows:

public interface Queue<E> extends Collection<E> {
  // 增加一个元素到队尾,如果队列已满,则抛出一个IIIegaISlabEepeplian异常
    boolean add(E e);
    // 添加一个元素到队尾并返回true,如果队列已满,则返回false
    boolean offer(E e);
    // 移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
    E remove();
    // 移除并返问队列头部的元素,如果队列为空,则返回null
    E poll();
    // 返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
    E element();
    // 返问队列头部的元素,如果队列为空,则返回null
    E peek()
}

The definition of Queue is similar to the definition of Stack. ArrayDeque is not a fixed-size queue, and it will expand every time the queue is full, and an exception will be thrown unless the expansion exceeds the bounds of int. So there is almost no difference between add and offer here.

// 底层用数组存储元素
private transient E[] elements;
// 队列的头部元素索引(即将pop出的一个)
private transient int head;
// 队列下一个要添加的元素索引
private transient int tail;
// 最小的初始化容量大小,需要为2的n次幂
private static final int MIN_INITIAL_CAPACITY = 8;

The MIN_INITIAL_CAPACITY requirement here is an integer power of 2. We know that in the Hashmap, the length of the bucket is an integer power of 2. What is the reason here?
Just look at the constructor of ArrayDeque.

/**
 * 默认构造方法,数组的初始容量为16
 */
public ArrayDeque() {
    elements = (E[]) new Object[16];
}

/**
 * 使用一个指定的初始容量构造一个ArrayDeque
 */
public ArrayDeque( int numElements) {
    allocateElements(numElements);
}

/**
 * 构造一个指定Collection集合参数的ArrayDeque
 */
public ArrayDeque(Collection<? extends E> c) {
    allocateElements(c.size());
    addAll(c);
}

/**
 * 分配合适容量大小的数组,确保初始容量是大于指定numElements的最小的2的n次幂
 */
private void allocateElements(int numElements) {
    int initialCapacity = MIN_INITIAL_CAPACITY;
    // 找到大于指定容量的最小的2的n次幂
    // Find the best power of two to hold elements.
    // Tests "<=" because arrays aren't kept full.
    // 如果指定的容量小于初始容量8,则执行一下if中的逻辑操作
    if (numElements >= initialCapacity) {
        initialCapacity = numElements;
        initialCapacity |= (initialCapacity >>>  1);
        initialCapacity |= (initialCapacity >>>  2);
        initialCapacity |= (initialCapacity >>>  4);
        initialCapacity |= (initialCapacity >>>  8);
        initialCapacity |= (initialCapacity >>> 16);
        initialCapacity++;

        if (initialCapacity < 0)   // Too many elements, must back off
            initialCapacity >>>= 1; // Good luck allocating 2 ^ 30 elements
    }
    elements = (E[]) new Object[initialCapacity];
}

The code here is copied from someone else, and I couldn't understand it at the beginning of the period. The general meaning is that every time you need to find an integer that is just larger than the integer power of 2 of numElementsd, you can calculate these bit operations yourself.

Enqueue operation

We often use add(), offer(), and call the methods addLast() and offerLast() respectively. The difference is that the methods of the add() series will throw an exception, while the methods of the offer() series will return true or false. The same is true for remove() poll().

/**
     * 增加一个元素,如果队列已满,则抛出一个IIIegaISlabEepeplian异常
     */
    public boolean add(E e) {
        // 调用addLast方法,将元素添加到队尾
        addLast(e);
        return true;
    }

     /**
     * 添加一个元素
     */
    public boolean offer(E e) {
        // 调用offerLast方法,将元素添加到队尾
        return offerLast(e);
    }

    /**
     * 在队尾添加一个元素
     */
    public boolean offerLast(E e) {
        // 调用addLast方法,将元素添加到队尾
        addLast(e);
        return true;
    }

    /**
     * 将元素添加到队尾
     */
    public void addLast(E e) {
        // 如果元素为null,咋抛出空指针异常
        if (e == null)
            throw new NullPointerException();
        // 将元素e放到数组的tail位置
        elements[tail ] = e;
        // 判断tail和head是否相等,如果相等则对数组进行扩容
        if ( (tail = (tail + 1) & ( elements.length - 1)) == head)
            // 进行两倍扩容
            doubleCapacity();
    }

(tail = (tail + 1) & ( elements.length - 1)) == head)The most exquisite!

Steal someone else's picture to explain.
We assume that the initial capacity of the queue is 8, and initial addition, A, B, C, D four elements. Corresponding to the array subscripts 0, 1, 2, and 3 respectively, but because this is a double-ended queue, after a period of time, it may become the situation on the right side of the picture above.
Then the tail reaches the end of the array, and if you add elements, what should you do, directly expand the capacity? Is there another paragraph ahead? The answer is that it will not expand, it will fill the elements to the missing positions in the front, and it will not expand until the array is really full. (tail = (tail + 1) & ( elements.length - 1)) will only == head when head and tail pointers are adjacent and then start expanding.Javaer们把这个叫做,循环数组!

Expansion of ArrayDeque

/**
 * 数组将要满了的时候(tail==head)将,数组进行2倍扩容
 */
private void doubleCapacity() {
    // 验证head和tail是否相等
    assert head == tail;
    int p = head ;
    // 记录数组的长度
    int n = elements .length;
    // 计算head后面的元素个数,这里没有采用jdk中自带的英文注释right,是因为所谓队列的上下左右,只是我们看的方位不同而已,如果上面画的图,这里就应该是left而非right
    int r = n - p; // number of elements to the right of p
    // 将数组长度扩大2倍
    int newCapacity = n << 1;
    // 如果此时长度小于0,则抛出IllegalStateException异常,什么时候newCapacity会小于0呢,前面我们说过了int值<<1越界
    if (newCapacity < 0)
        throw new IllegalStateException( "Sorry, deque too big" );
    // 创建一个长度是原数组大小2倍的新数组
    Object[] a = new Object[newCapacity];
    // 将原数组head后的元素都拷贝值新数组
    System. arraycopy(elements, p, a, 0, r);
    // 将原数组head前的元素都拷贝到新数组
    System. arraycopy(elements, 0, a, r, p);
    // 将新数组赋值给elements
    elements = (E[])a;
    // 重置head为数组的第一个位置索引0
    head = 0;
    // 重置tail为数组的最后一个位置索引+1((length - 1) + 1)
    tail = n;
}

Two copies are made because the array is divided into two segments.

out of the team

/**
 * 移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
 */
public E remove() {
    // 调用removeFirst方法,移除队头的元素
    return removeFirst();
}

/**
 * @throws NoSuchElementException {@inheritDoc}
 */
public E removeFirst() {
    // 调用pollFirst方法,移除并返回队头的元素
    E x = pollFirst();
    // 如果队列为空,则抛出NoSuchElementException异常
    if (x == null)
        throw new NoSuchElementException();
    return x;
}

/**
 * 移除并返问队列头部的元素,如果队列为空,则返回null
 */
public E poll() {
    // 调用pollFirst方法,移除并返回队头的元素
    return pollFirst();
}

public E pollFirst() {
    int h = head ;
    // 取出数组队头位置的元素
    E result = elements[h]; // Element is null if deque empty
    // 如果数组队头位置没有元素,则返回null值
    if (result == null)
        return null;
    // 将数组队头位置置空,也就是删除元素
    elements[h] = null;     // Must null out slot
    // 将head指针往前移动一个位置
    head = (h + 1) & (elements .length - 1);
    // 将队头元素返回
    return result;
}

PriorityQueue

When I write some algorithm topics, especially some tricky search questions in interviews (Google has an unordered array, finding the K-th largest element is achieved by constructing a K heap), I will use heap sorting. In fact, JDK A priority queue similar to a pair structure has been encapsulated for us. This PriorityQueue is also called a binary heap.

In fact, it is a complete binary tree (binary heap), and its characteristics are: before the nth layer depth is filled, the n+1th layer depth will not start to be filled, and the element insertion is filled from left to right.
  Based on this feature, the binary heap can be represented by an array instead of a linked list.

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
}

underlying storage

// 默认初始化大小 
privatestaticfinalintDEFAULT_INITIAL_CAPACITY = 11;

//  
/**
 * Priority queue represented as a balanced binary heap: the two
 * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
 * priority queue is ordered by comparator, or by the elements'
 * natural ordering, if comparator is null: For each node n in the
 * heap and each descendant d of n, n <= d.  The element with the
 * lowest value is in queue[0], assuming the queue is nonempty.
 */
private transient Object[] queue ;

// 队列的大小
private int size = 0;

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

// 版本号
private transient int modCount = 0;

看上面的英文注释,我的英语不好也看懂了,哈哈。

PriorityQueue()

public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null); // null 为比较器,默认null
}

public PriorityQueue( int initialCapacity) {
    this(initialCapacity, null);
}

/**
 * 使用指定的初始大小和比较器来构造一个优先队列
 */
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
    // 初始大小不允许小于1
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    // 使用指定初始大小创建数组
    this.queue = new Object[initialCapacity];
    // 初始化比较器
    this.comparator = comparator;
}

/**
 * 构造一个指定Collection集合参数的优先队列
 */
public PriorityQueue(Collection<? extends E> c) {
    // 从集合c中初始化数据到队列
    initFromCollection(c);
    // 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparator
    if (c instanceof SortedSet)
        comparator = (Comparator<? super E>)
            ((SortedSet<? extends E>)c).comparator();
    else if (c instanceof PriorityQueue)
        comparator = (Comparator<? super E>)
            ((PriorityQueue<? extends E>)c).comparator();
    //  如果集合c没有包含比较器,则默认比较器Comparator为空
    else {
        comparator = null;
        // 调用heapify方法重新将数据调整为一个二叉堆
        heapify();
    }
}

/**
 * 构造一个指定PriorityQueue参数的优先队列
 */
public PriorityQueue(PriorityQueue<? extends E> c) {
    comparator = (Comparator<? super E>)c.comparator();
    initFromCollection(c);
}

/**
 * 构造一个指定SortedSet参数的优先队列
 */
public PriorityQueue(SortedSet<? extends E> c) {
    comparator = (Comparator<? super E>)c.comparator();
    initFromCollection(c);
}

/**
 * 从集合中初始化数据到队列
 */
private void initFromCollection(Collection<? extends E> c) {
    // 将集合Collection转换为数组a
    Object[] a = c.toArray();
    // If c.toArray incorrectly doesn't return Object[], copy it.
    // 如果转换后的数组a类型不是Object数组,则转换为Object数组
    if (a.getClass() != Object[].class)
        a = Arrays. copyOf(a, a.length, Object[]. class);
    // 将数组a赋值给队列的底层数组queue
    queue = a;
    // 将队列的元素个数设置为数组a的长度
    size = a.length ;
}

add() and offer()

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
  //如果压入的元素为null 抛出异常      
    int i = size;
    if (i >= queue.length)
        grow(i + 1);
        //如果数组的大小不够扩充
    size = i + 1;
    if (i == 0)
        queue[0] = e;
        //如果只有一个元素之间放在堆顶
    else
        siftUp(i, e);
        //否则调用siftUp函数从下往上调整堆。
    return true;
}

Both of these methods add an element to this small heap. The body of the add() method is also the call to offer().

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

The source code of offer() is as follows:

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
  //如果压入的元素为null 抛出异常      
    int i = size;
    if (i >= queue.length)
        grow(i + 1);
        //如果数组的大小不够扩充
    size = i + 1;
    if (i == 0)
        queue[0] = e;
        //如果只有一个元素之间放在堆顶
    else
        siftUp(i, e);
        //否则调用siftUp函数从下往上调整堆。
    return true;
}

The exchange process is similar to that in the heap sort I wrote myself.
1. The priority queue cannot store null
2. After the elements are put into the heap, if the size of the array is not enough, expand the array.
3. Adjust the heap from bottom to top. siftUp(i,e)

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;
}

It is a process of constant comparison and exchange, so I won't repeat it.

Poll() remove()

The poll method deletes a node from the head of the PriorityQueue at a time, that is, deletes a node from the top of the small top heap, and remove() can not only delete the head node but also use remove(Object o) to delete and give in the heap. The first occurrence of the same object as the given object.
The poll() method is easy to understand, removes the top element of the heap, and then adjusts the heap.

public E poll() {
    if (size == 0)
        return null;
    //如果堆大小为0则返回null      
    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;
}

Similarly, siftDown() adjusts the heap from the top of the heap down.

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) {
        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;
}

size transformation

If you want to use priorityQueue to construct the maximum heap, you can just write a Comparator and pass it into the constructor. The specific constructor has already been said before.

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;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326523885&siteId=291194637