The problem

(1) What is a double-endian queue?

(2) How ArrayDeque implements a double-endian queue?

(3) Is ArrayDeque thread safe?

(4) Is ArrayDeque bounded?

Introduction to the

A double-endian queue is a special queue in which both ends can get in and out of elements, hence its name.

An ArrayDeque is a two-endian queue implemented as an array that is not thread-safe.

Inheritance system

From the inheritance architecture, ArrayDeque implements the Deque interface, which is an enhancement of Queue, inherited from Queue.


public interface Deque<E> extends Queue<E> {
    // Add an element to the queue header
    void addFirst(E e);
    // Add elements to the end of the queue
    void addLast(E e);
    // Add an element to the queue header
    boolean offerFirst(E e);
    // Add elements to the end of the queue
    boolean offerLast(E e);
    // Remove the element from the queue header
    E removeFirst(a);
    // Remove the element from the end of the queue
    E removeLast(a);
    // Remove the element from the queue header
    E pollFirst(a);
    // Remove the element from the end of the queue
    E pollLast(a);
    // View the queue header element
    E getFirst(a);
    // Look at the end of the queue
    E getLast(a);
    // View the queue header element
    E peekFirst(a);
    // Look at the end of the queue
    E peekLast(a);
    // Removes the specified element from the queue head by traversing backwards
    boolean removeFirstOccurrence(Object o);
    Removes the specified element from the end of the queue by traversing forward
    boolean removeLastOccurrence(Object o);

    // *** the method in the queue ***
    
    // Add element, equal to addLast(e)
    boolean add(E e);
     // Add element, equal to offerLast(e)
    boolean offer(E e);
    // Remove elements, equal to removeFirst()
    E remove(a);
    // Remove elements, equal to pollFirst()
    E poll(a);
    // View the element, equal to getFirst()
    E element(a);
    // View the element, equal to peekFirst()
    E peek(a);

    // *** * stack method ***

    // addFirst(e)
    void push(E e);
    // removeFirst()
    E pop(a);

    // *** methods in Collection ***
    
    RemoveFirstOccurrence (o)
    boolean remove(Object o);
    // Check whether an element is included
    boolean contains(Object o);
    // Number of elements
    public int size(a);
    / / the iterator
    Iterator<E> iterator(a);
    // reverse iterator
    Iterator<E> descendingIterator(a);
}
Copy the code

The following classes of methods have been added to the Deque:

(1) *First, operating on elements from the queue header;

(2) *Last, operating on elements from the end of the queue;

(3) Push (e), pop(), the method of manipulating elements in the way of stack;

Source code analysis

The main properties

// An array of elements
transient Object[] elements; // non-private to simplify nested class access
// Queue header position
transient int head;
// The end of the queue
transient int tail;
// Minimum initial capacity
private static final int MIN_INITIAL_CAPACITY = 8;
Copy the code

As you can see from the properties, ArrayDeque uses arrays to store elements and uses a header and tail pointer to identify the head and tail of a queue, with a minimum size of 8.

Main construction methods

// The default constructor with an initial capacity of 16
public ArrayDeque(a) {
    elements = new Object[16];
}
// Specifies the number of elements to initialize
public ArrayDeque(int numElements) {
    allocateElements(numElements);
}
// Initialize the elements in collection C into an array
public ArrayDeque(Collection<? extends E> c) {
    allocateElements(c.size());
    addAll(c);
}
// Initialize the array
private void allocateElements(int numElements) {
    elements = new Object[calculateSize(numElements)];
}
// To calculate the capacity, the logic of this code is to calculate the nearest 2 to the n power greater than numElements and not less than 8
For example, 3 is 8,9 is 16, and 33 is 64
private static int calculateSize(int numElements) {
    int initialCapacity = MIN_INITIAL_CAPACITY;
    // Find the best power of two to hold elements.
    // Tests "<=" because arrays aren't kept full.
    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
    }
    return initialCapacity;
}
Copy the code

From the constructor, we know that the default initial capacity is 16 and the minimum capacity is 8.

The team

There are many ways to join a team, and we’re going to focus on two, addFirst(e) and addLast(e).

// Join the queue from the head
public void addFirst(E e) {
    // Null elements are not allowed
    if (e == null)
        throw new NullPointerException();
    // Decrement the head pointer by 1 and modulo the array length by 1
    // This is to prevent the array from reaching the end of the boundary overflow
    // If you come to the end, start from the end
    // loop through an array
    elements[head = (head - 1) & (elements.length - 1)] = e;
    // If the heads and tails are together, expand the capacity
    // The expansion rule is also simple, directly double
    if (head == tail)
        doubleCapacity();
}
// Join the queue from the back
public void addLast(E e) {
    // Null elements are not allowed
    if (e == null)
        throw new NullPointerException();
    // Place the element at the end of the pointer
    // You can see that the tail pointer points to the position next to the last element in the queue
    elements[tail] = e;
    // the tail pointer is incremented by 1, starting from the beginning if it reaches the end of the array
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}
Copy the code

(1) There are two ways to join the queue, from the head of the queue or from the end of the queue;

(2) If the capacity is not enough, directly expand to twice;

(3) make the head and tail Pointers loop in the array range by taking the mode of module;

(4) x & (len-1) = x % len;

capacity

private void doubleCapacity(a) {
    assert head == tail;
    // The position of the header pointer
    int p = head;
    // The old array length
    int n = elements.length;
    // The distance between the header pointer and the end of the array
    int r = n - p; // number of elements to the right of p
    // The new length is twice the old length
    int newCapacity = n << 1;
    // Determine whether overflow occurs
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    // Create a new array
    Object[] a = new Object[newCapacity];
    // Copy the elements after the old array head into the new array
    System.arraycopy(elements, p, a, 0, r);
    // Copy the elements between subscripts 0 and head into the new array
    System.arraycopy(elements, 0, a, r, p);
    // Assign to a new array
    elements = a;
    // head points to 0 and tail points to the old array length
    head = 0;
    tail = n;
}
Copy the code

It may be a bit tricky to migrate elements here, but look at the picture below to understand.

Out of the team

PollFirst () and pollLast().

// Exit the queue from the queue head
public E pollFirst(a) {
    int h = head;
    @SuppressWarnings("unchecked")
    // take the queue header element
    E result = (E) elements[h];
    If the queue is empty, null is returned
    if (result == null)
        return null;
    // Set the queue head to null
    elements[h] = null;     // Must null out slot
    // Move the queue header pointer to the right one bit
    head = (h + 1) & (elements.length - 1);
    // Return the obtained element
    return result;
}
// From the end of the queue
public E pollLast(a) {
    // Move the tail pointer to the left one bit
    int t = (tail - 1) & (elements.length - 1);
    @SuppressWarnings("unchecked")
    // Take the element at the current tail pointer
    E result = (E) elements[t];
    // Return null if the queue is empty
    if (result == null)
        return null;
    // Make the current tail pointer null
    elements[t] = null;
    // tail points to the new tail pointer
    tail = t;
    // Return the obtained element
    return result;
}
Copy the code

(1) There are two ways to exit the queue, from the head of the queue or from the tail of the queue;

(2) make the head and tail Pointers loop in the array range by taking the mode of module;

(3) No shrinkage after the team haha ^^

The stack

When we introduced deques earlier, we said that deques can be used directly as stacks, so how is ArrayDeque implemented?

public void push(E e) {
    addFirst(e);
}

public E pop(a) {
    return removeFirst();
}
Copy the code

Isn’t it very simple, in and out of the stack as long as the operation queue header can be ok.

conclusion

(1) ArrayDeque is a double-ended queue implemented by array;

(2) ArrayDeque is implemented by circulating the array with the head and tail Pointers;

(3) When the capacity of ArrayDeque is insufficient, the capacity will be expanded, and the capacity will be doubled each time;

(4) ArrayDeque can be used as a stack directly;

eggs

Double-endian queue versus dual queue?

A double-endian queue (Deque) is a queue in which both ends of the queue can move elements in and out, and the actual elements are stored inside.

Dual Queue refers to a Queue that serves two purposes. The nodes in the Queue are divided into data nodes and non-data nodes. It is the data structure used by the LinkedTransferQueue.

Remember LinkedTransferQueue? Click on the link to the source analysis of LinkedTransferQueue.


Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.