preface

A Queue is a data structure with a first in, first out (FIFO) feature, and PriorityQueue is a subclass of it. Unlike a first in, first out (FIFO), PriorityQueue can control the output order (priority) of elements through a comparator. This article will take a look at the source code for PriorityQueuede and see how it is implemented.

Class inheritance relation

Let’s start with the Queue interface:

public interface Queue<E> extends Collection<E>
Copy the code

The Queue interface inherits the Collection interface, which represents a Collection. It provides three methods: add, delete, and get, each with two implementations.

// Add elements
boolean add(E e);
boolean offer(E e);
// Delete elements
E remove(a);
E poll(a);
// Get the element
E element(a);
E peek(a);
Copy the code

The difference between the two implementations is that Add, Remove, and Element all throw one more exception than the corresponding Offer, poll, and peek. For the Add method, IllegalStateException is thrown if there is not enough capacity to add a new element, and offer returns false. For the remove method, NoSuchElementException is thrown if the queue is empty, and the poll method returns NULL. For the Element method, NoSuchElementException is thrown if the queue is empty, and the PEEK method returns NULL.

Now look at the PriorityQueue class:

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable
Copy the code

Well, instead of implementing Queue directly, we’re going to inherit AbstractQueue. Again, AbstractQueue should also be a template abstract class (similar to AbstractMap and AbstractList). To see AbstractQueue:

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E>
Copy the code

Sure enough, AbstractCollection class is inherited and Queue interface is implemented. If it’s a template class there must be template methods. The AbstractQueue source code only implements the add, remove, and elemet methods.

public boolean add(E e) {
        if (offer(e)) / / call the offer. }public E remove(a) {
        E x = poll(); / / call the poll. }public E element(a) {
        E x = peek(); / / peek. }Copy the code

As you can see, they call Offer, poll, and peek, respectively. That is, if you want to implement a queue with AbstractQueue, you must implement the Offer, poll, and PEEK methods. The following is a tight analysis of PriorityQueue source code.

PriorityQueue source code analysis

Looking at the source code, PriorityQueue is implemented using a “maximum priority heap”, where the top element of the heap is the element with the highest priority. Kind of integrated big root heap and small root heap function.

Important attributes

By the nature of the heap, the storage structure must be an array; Since different priorities are supported, there must be a comparator, that is, support for custom sorting and sequential sorting.

// The default capacity is 11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// The storage structure of the heap, storing elements
transient Object[] queue; // Not serializable
// The number of elements currently stored
int size;
// Set priority
private final Comparator<? super E> comparator;
// Avoid OOM, the maximum size of the array can be allocated
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Copy the code

The constructor

There are many constructors for PriorityQueue. The main arguments are capacity and comparator:

public PriorityQueue(a) {
        this(DEFAULT_INITIAL_CAPACITY, null); // Default natural order
    }

public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null); // Customize the initial capacity
    }
    
public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator); // Custom comparator
    }
    
public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {...// Assign the corresponding value
    }

public PriorityQueue(Collection<? extends E> c) {
        // roll c into a heap
        if (c instanceof SortedSet<?>) {
            ... // SortedSet comes with a comparator
        }
        else if (c instanceof PriorityQueue<?>) {
            ... // PriorityQueue has its own comparator
        }
        else{... }}public PriorityQueue(PriorityQueue<? extends E> c) {...// Get the comparator from PriorityQueue and roll c into a heap
    }
    
public PriorityQueue(SortedSet<? extends E> c) {...// Get the comparator from SortedSet and roll c into a heap
    }
Copy the code

Reuse method

PriorityQueue supports capacity expansion.

// minCapacity Indicates the minimum capacity required
private void grow(int minCapacity) {
        int oldCapacity = queue.length; // Get the current capacity
        // Double size if small; else grow by 50%
        If the old capacity is less than 64, increase the old capacity by +2
        If the old capacity is greater than or equal to 64, increase the size of the old capacity by half
        int newCapacity = oldCapacity + ((oldCapacity < 64)? (oldCapacity +2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0) // This handles large volumes
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity); // Copy stored data
    }
Copy the code

The size of each expansion is still quite large, either doubling the original size or increasing by half.

Before we look at how to add, delete, and get elements, here are a few things to know:

  1. The subscript of the last non-leaf node of a complete binary tree is (n-2) / 2;
  2. In a complete binary tree, if the subscript of a non-leaf node is I, its parent is subscript (i-1) /2, its left child is subscript 2 * I + 1, and its right child is subscript 2 * I + 2.

Offer method

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1); // If the current capacity is exceeded, expand the capacity
        siftUp(i, e); // New elements are added to the end of the array and then moved up to build the heap
        size = i + 1; // The current size is increased by 1
        return true;
    }
Copy the code

The siftUp method will eventually call either a siftUpUsingComparator or a siftUpComparable method, which are similar implementations, so let’s look directly at the siftUpUsingComparator method.

// To move up is to constantly compare with the parent
private static <T> void siftUpUsingComparator(
        int k, T x, Object[] es, Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) > > >1; // parent subscript
            Object e = es[parent];
            if (cmp.compare(x, (T) e) >= 0) // If the priority is higher, the comparison continues
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = x;
    }
Copy the code

Every time you add an element, keep the heap sorted.

Poll method

public E poll(a) {
        final Object[] es;
        final E result;
        // Return the top of the heap element
        if ((result = (E) ((es = queue)[0))! =null) {
            modCount++;
            final int n;
            final E x = (E) es[(n = --size)]; // Change the tail element to the first one
            es[n] = null;
            if (n > 0) {
                final Comparator<? super E> cmp;
                if ((cmp = comparator) == null) // Move down to adjust the natural sequence
                    siftDownComparable(0, x, es, n);
                else // Custom order down adjustment
                    siftDownUsingComparator(0, x, es, n, cmp); }}return result;
    }
Copy the code

The poll method returns the first element (the top of the heap) and deletes the element from the heap. The deletion process is to replace the first element with the last, and then move it down. Here’s a direct look at the siftDownUsingComparator method:

private static <T> void siftDownUsingComparator(
        int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
        // assert n > 0;
        int half = n >>> 1; // Subscript the last non-leaf node (since size has been reduced by 1)
        while (k < half) {
            int child = (k << 1) + 1; / / the left child
            Object c = es[child];
            int right = child + 1; / / right child
            if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
                c = es[child = right]; // Choose the one with the highest priority from the left and right children
            if (cmp.compare(x, (T) c) <= 0)
                break;
            es[k] = c; // Move the target element down
            k = child;
        }
        es[k] = x;
    }
Copy the code

Heap adjustments are made every time the head of the queue element is popped up.

The remove method

The poll method can be seen as a special case of the remove method, which permanently removes the first element.

public boolean remove(Object o) {
        int i = indexOf(o); // Locate the element to be deleted
        if (i == -1)
            return false;
        else {
            removeAt(i); // Remove the element at the specified location
            return true; }}Copy the code

The removeAt method is called:

E removeAt(int i) {
        // assert i >= 0 && i < size;
        final Object[] es = queue;
        modCount++;
        int s = --size; // size has been reduced by 1
        if (s == i) // removed last element
            es[i] = null; // The last element has been deleted
        else {
            E moved = (E) es[s]; / / stern element
            es[s] = null;
            siftDown(i, moved); // Specify the element to replace the trailing element, and then adjust
            if (es[i] == moved) {
                siftUp(i, moved); // Move up if the specified position is changed to a trailing element (no move down occurs)
                if(es[i] ! = moved)returnmoved; }}return null; // Return null for normal deletion
    }
Copy the code

conclusion

  1. PriorityQueue is implemented based on the maximum priority heap, which can be a large or small root heap depending on the comparator;
  2. PriorityQueue does not support NULL;
  3. PriorityQueue is not thread-safe and can be used in multithreaded environmentsjava.util.concurrent.PriorityBlockingQueue;
  4. useiterator()Traversal, does not guarantee that the output sequence is ordered, in fact, traversal is the storage array.

Pay attention to wechat public number, the latest technology dry goods real-time push