[Learning Notes - Java Collection - 14] Queue - PriorityQueue Source Analysis

Posted by btubalinal on Tue, 20 Aug 2019 18:12:05 +0200

introduce

A priority queue is a collection of 0 or more elements in which each element has a weight value and each time it leaves the queue, the element with the highest or lowest priority pops up.

Generally speaking, priority queues are implemented using heaps.

Source Code Analysis

Main attributes


  // Default capacity
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    // Where elements are stored
    transient Object[] queue; // non-private to simplify nested class access
    // Number of elements
    private int size = 0;
    // comparator
    private final Comparator<? super E> comparator;
    // Number of Modifications
    transient int modCount = 0; // non-private to simplify nested class access
  1. The default capacity is 11;
  2. queue, elements are stored in arrays, which is consistent with what we said earlier about heaps that generally use arrays for storage;
  3. A comparator, a comparator, also compares elements in a priority queue in two ways: one is the natural order of the elements, the other is the comparison through the comparator;
  4. modCount, number of modifications, this property indicates that PriorityQueue is also fast-fail;

Entry

There are two ways to enlist, add (E) and offer (E), which are identical, and add (E) is also the offer (E) that is called.

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

public boolean offer(E e) {
    // null element is not supported
    if (e == null)
        throw new NullPointerException();
    modCount++;
    // Take size
    int i = size;
    // Number of elements reached maximum capacity, expanding capacity
    if (i >= queue.length)
        grow(i + 1);
    // Number of elements plus 1
    size = i + 1;
    // If there are no elements yet
    // Insert directly into the first position of the array
    // This is different from what we talked about before
    // java starts with zero inside
    // The heap we're talking about starts with one
    if (i == 0)
        queue[0] = e;
    else
        // Otherwise, insert the element to the position of the array size, which is next to the last element
        // Notice that the size here is not the array size, but the number of elements
        // Then, do a bottom-up heap
        siftUp(i, e);
    return true;
}

private void siftUp(int k, E x) {
    // Use different methods depending on whether there is a comparator
    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) {
        // Location of parent node found
        // Because elements start at 0, subtract 1 and divide by 2
        int parent = (k - 1) >>> 1;
        // Value of parent node
        Object e = queue[parent];
        // Compare values of inserted elements to parent nodes
        // Jump out of loop if larger than parent node
        // Otherwise swap locations
        if (key.compareTo((E) e) >= 0)
            break;
        // Swap location with parent node
        queue[k] = e;
        // The inserted element position is now moved to the parent node position
        // Continue comparing with parent node
        k = parent;
    }
    // Find the place where you should insert the element
    queue[k] = key;
}
  1. null elements are not allowed in the queue;
  2. If the array is not enough, expand it first;
  3. If there are no elements, insert the position of subscript 0;
  4. If there are any elements, insert them into a position behind the last element (there is no actual insertion of a hash);
  5. Heap from bottom to top, always comparing with the parent node;
  6. If it is smaller than the parent node, the position is swapped with the parent until it is larger than the parent node.
  7. Thus, PriorityQueue is a small top heap.

Expansion

private void grow(int minCapacity) {
    // Old capacity
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    // Double capacity when old capacity is less than 64
    // Old capacity greater than or equal to 64, capacity increases by only half of old capacity
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    // Check for overflow
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // Create a new array of new capacity size and copy the old array elements to the past
    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. Each expansion capacity doubles when the array is smaller (less than 64);
  2. When the array is large, the capacity of each expansion is only increased by half.

Queue

There are two ways to queue, remove() and poll(), which are also called poll(), but throw an exception when there are no elements.


public E remove() {
    // Call poll to pop up the first element
    E x = poll();
    if (x != null)
        // Return pop-up elements when there are elements
        return x;
    else
        // Throw an exception without an element
        throw new NoSuchElementException();
}

@SuppressWarnings("unchecked")
public E poll() {
    // If size is 0, there are no elements
    if (size == 0)
        return null;
    // Pop-up element, number of elements minus 1
    int s = --size;
    modCount++;
    // Queue head element
    E result = (E) queue[0];
    // End Queue Element
    E x = (E) queue[s];
    // Delete the last element of the queue
    queue[s] = null;
    // If there are elements after pop-up
    if (s != 0)
        // Move the last element of the queue to the top of the queue
        // Do top-down heaping again
        siftDown(0, x);
    // Return pop-up elements
    return result;
}

private void siftDown(int k, E x) {
    // Choose a different method depending on whether there is a comparator
    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;
    // You only need to compare half because leaf nodes account for half of the elements
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        // Find the location of the child node, where 1 is added because the element starts at position 0
        int child = (k << 1) + 1; // assume left child is least
        // Value of left child node
        Object c = queue[child];
        // Location of the right child node
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            // Left and right nodes are smaller
            c = queue[child = right];
        // End if smaller than child nodes
        if (key.compareTo((E) c) <= 0)
            break;
        // Swap locations if larger than the smallest child node
        queue[k] = c;
        // Pointer moves to the smallest child node and continues to compare
        k = child;
    }
    // Find the right place to put elements
    queue[k] = key;
}

  1. Eject the first element of the queue;
  2. Move the last element of the queue to the top of the queue;
  3. Heap up and down, comparing down to the smallest child node;
  4. If it is larger than the smallest child node, the position is swapped and the comparison with the smallest child node continues.
  5. If it is smaller than the smallest child node, the heap ends without swapping locations.
  6. This is the deletion of the top element in the heap;

Take the first element of the queue

There are two ways to get the first element, element() and peek(), which are also called peek(), except that an exception is thrown when the element is not fetched.

public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
public E peek() {
    return (size == 0) ? null : (E) queue[0];
}
  1. If there are any elements, remove the element of subscript 0;
  2. If there are no elements, null is returned and element() throws an exception.

summary

  1. PriorityQueue is a small top heap;
  2. PriorityQueue is non-thread safe;
  3. PriorityQueue is not ordered, only the smallest elements are stored on top of the heap;
  4. Queuing is the implementation of the insert element of the heap.
  5. Queuing is the implementation of deleted elements of a heap.

Topics: Java less