problem
(1) What is a double-ended queue?
(2) How does Array Deque achieve double-ended queues?
(3) Is Array Deque thread-safe?
(4) Is Array Deque bounded?
brief introduction
Double-ended queue is a special kind of queue. It can enter and exit elements at both ends, so it is named double-ended queue.
Array Deque is a double-ended queue implemented in an array manner, which is non-thread-safe.
Inheritance system
It can be seen from the inheritance system that Array Deque implements the Deque interface, which inherits from the Queue interface, which is an enhancement of Queue.
public interface Deque<E> extends Queue<E> { // Add elements to the queue head void addFirst(E e); // Add elements to the end of the queue void addLast(E e); // Add elements to the queue head boolean offerFirst(E e); // Add elements to the end of the queue boolean offerLast(E e); // Remove elements from the queue head E removeFirst(); // Remove elements from the end of the queue E removeLast(); // Remove elements from the queue head E pollFirst(); // Remove elements from the end of the queue E pollLast(); // View Queue Header Elements E getFirst(); // Look at the end of the queue element E getLast(); // View Queue Header Elements E peekFirst(); // Look at the end of the queue element E peekLast(); // Remove the specified element from the queue head by traversing backwards boolean removeFirstOccurrence(Object o); // Remove the specified element by traversing forward from the end of the queue boolean removeLastOccurrence(Object o); // Method in the *** Queue *** // Add element, equal to addLast(e) boolean add(E e); // Add element, equal to offer Last (e) boolean offer(E e); // Remove the element, equal to removeFirst() E remove(); // Remove the element, equal to pollFirst() E poll(); // View the element, equal to getFirst() E element(); // View the element, equal to peekFirst() E peek(); // *** Stack Method*** // On the stack, equal to addFirst(e) void push(E e); // Out of the stack, equal to removeFirst() E pop(); // Method in *** Collection *** // Delete the specified element, equal to removeFirstOccurrence(o) boolean remove(Object o); // Check whether an element is included boolean contains(Object o); // Element number public int size(); // iterator Iterator<E> iterator(); // reverse iterator Iterator<E> descendingIterator(); }
The following new methods have been added to Deque:
(1) * First, which means to manipulate elements from the queue head;
(2) * Last, which means to manipulate elements from the end of the queue;
(3) push(e), pop(), the way to manipulate elements by stack;
Source code analysis
Main attributes
// An array of storage elements transient Object[] elements; // non-private to simplify nested class access // Queue head position transient int head; // Queue tail position transient int tail; // Minimum initial capacity private static final int MIN_INITIAL_CAPACITY = 8;
From the attributes, we can see that ArrayDeque uses arrays to store elements and uses head and tail pointers to identify the head and tail of the queue, with a minimum capacity of 8.
Main construction methods
// Default construction method with initial capacity of 16 public ArrayDeque() { elements = new Object[16]; } // Initialization of specified number of elements public ArrayDeque(int numElements) { allocateElements(numElements); } // Initialize elements in set c into arrays public ArrayDeque(Collection<? extends E> c) { allocateElements(c.size()); addAll(c); } // Initialize arrays private void allocateElements(int numElements) { elements = new Object[calculateSize(numElements)]; } // Computing capacity, the logic of this code is to compute the closest n-th power greater than numElements and no less than 8 // For example, 3 is 8, 9 is 16, 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; }
Through the construction method, we know that the default initial capacity is 16 and the minimum capacity is 8.
Join the team
There are many ways to join the team. Here we mainly analyze two ways, addFirst(e) and addLast(e).
// Enter from the queue public void addFirst(E e) { // No null element allowed if (e == null) throw new NullPointerException(); // Reduce the head pointer by 1 and the array length by 1 // This is to prevent boundary overflow at the end of the array // If it comes to an end, go from the end to the front // Equivalent to recycling arrays elements[head = (head - 1) & (elements.length - 1)] = e; // If the head and tail are close together, the capacity will be expanded. // The expansion rule is also simple, directly twice the size. if (head == tail) doubleCapacity(); } // Enter from the end of the queue public void addLast(E e) { // No null element allowed if (e == null) throw new NullPointerException(); // Place the element in the tail pointer position // You can see that the tail pointer points to the next position of the last element of the queue elements[tail] = e; // tail pointer plus 1, starting from the beginning if it reaches the end of the array if ( (tail = (tail + 1) & (elements.length - 1)) == head) doubleCapacity(); }
(1) There are two ways to enter the queue, from the head of the queue or from the tail of the queue;
(2) If the capacity is insufficient, it will be doubled directly.
(3) Circulate the head and tail pointers in the range of arrays by modularization.
(4) X & (len - 1) = x% len, which is faster to use;
Capacity expansion
private void doubleCapacity() { assert head == tail; // Position of Head Pointer int p = head; // Old array length int n = elements.length; // The distance between the head pointer and the end of the array int r = n - p; // number of elements to the right of p // The new length is twice as long as the old one. int newCapacity = n << 1; // Judging whether or not spillover occurs if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); // 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 the old array subscript 0 and head to the new array System.arraycopy(elements, 0, a, r, p); // Assignment to a new array elements = a; // head points to 0, tail points to the position represented by the length of the old array head = 0; tail = n; }
The migration elements may be somewhat circumscribed here. See the picture below to understand.
Team out
There are also many ways to get out of the team. We mainly look at two ways, pollFirst() and pollLast().
// Out of the queue public E pollFirst() { int h = head; @SuppressWarnings("unchecked") // Take the queue head element E result = (E) elements[h]; // If the queue is empty, return null if (result == null) return null; // Leave the queue empty elements[h] = null; // Must null out slot // Queue head pointer moved one bit to the right head = (h + 1) & (elements.length - 1); // Returns the element obtained return result; } // Get out of line at the end of the queue public E pollLast() { // Tail pointer moved one bit to the left 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; // Dispose the current tail pointer to null elements[t] = null; // Tail points to the new tail pointer tail = t; // Returns the element obtained return result; }
(1) There are two ways to get out of the queue, from the head of the queue or from the tail of the queue;
(2) Circulate the head and tail pointers in the range of arrays by modularization.
(3) Haha did not shrink after leaving the team.^^
Stack
When we introduced Deque earlier, we said that Deque can be used directly as a stack, so how does Array Deque work?
public void push(E e) { addFirst(e); } public E pop() { return removeFirst(); }
Is it very simple, as long as the queue head is operated in the stack and out of the stack?
summary
(1) Array Deque is a double-ended queue implemented by array.
(2) Array Deque's entry into the queue is achieved by using arrays with head and tail pointers.
(3) When the capacity of Array Deque is insufficient, it will expand, doubling the capacity of each expansion;
(4) Array Deque can be used directly as a stack;
Egg
Double-ended queue and double-ended queue?
Deque refers to a queue in which both ends of the queue can enter and exit elements, in which the real elements are stored.
Dual Queue refers to a kind of queue which has two purposes. The nodes in it are divided into data nodes and non-data nodes. It is the data structure used by LinkedTransferQueue.
Remember LinkedTransferQueue? Click on the link to go directly[ LinkedTransferQueue Source Analysis of Dead java Sets].
Welcome to pay attention to my public number "Tong Ge Reads Source Code". Check out more articles about source code series and enjoy the sea of source code with Tong Ge.