Deque用法及原理講解
Deque用法及原理講解
最近想著對現有知識點進行一個總結,決定從集合開始,想想便從Deque開始吧,Deque用的比較少,但是還是一個功能十分強大的佇列,這種雙向佇列即可以支援先進後出,也能支援先進先出的格式,相當於同時實現了Stack和Vector,今天就來講一講Deque用法以及底層原始碼。
1. Deque用法
先寫一個簡單的demo,這個demo也是以前查Deque時看別人寫的,然後對著寫了一遍,如下:
package cn.com.queue.deque;
import java.util.ArrayDeque;
import java.util. Deque;
/**
* @author xiaxuan
*/
public class DequeTest {
public static void main(String[] args) {
Deque<Integer> mDeque = new ArrayDeque<>();
for (int i = 0; i < 5; i++) {
mDeque.offer(i);
}
System.out.println(mDeque.peek());
System. out.println("********集合方式遍歷*********");
//集合方式遍歷,元素不會被移除
for (Integer x : mDeque) {
System.out.println(x);
}
System.out.println("********遍歷佇列***********");
//佇列方式遍歷,元素逐個被移除
while (mDeque.peek() != null) {
System.out.println(mDeque. poll());
}
System.out.println("**********進棧操作*********");
mDeque.push(10);
mDeque.push(15);
mDeque.push(24);
print(mDeque);
System.out.println("*********出棧操作***********");
System.out.println(mDeque.pop());
}
public static void print(Deque<Integer> queue) {
//集合方式遍歷,元素不會被移除
for (Integer x : queue) {
System.out.println(x);
}
}
}
執行結果如下圖:
上圖中
2. Deque原理講解
首先我們看Deque的實現類ArrayQueue的資料結構,可以看到ArrayQueue還是使用陣列的結構,應該來說陣列是實現集合類的基礎資料結構。
/**
* The array in which the elements of the deque are stored.
* The capacity of the deque is the length of this array, which is
* always a power of two. The array is never allowed to become
* full, except transiently within an addX method where it is
* resized (see doubleCapacity) immediately upon becoming full,
* thus avoiding head and tail wrapping around to equal each
* other. We also guarantee that all array cells not holding
* deque elements are always null.
*/
transient Object[] elements; // non-private to simplify nested class access
現在看ArrayQueue的offer操作,原始碼如下:
/**
* Inserts the specified element at the end of this deque.
*
* <p>This method is equivalent to {@link #offerLast}.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
return offerLast(e);
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
這裡的offer操作實際上就是把元素新增到陣列的頭部, 空間不夠了則再對空間進行擴容,擴容的操作就不講了,實際上就是陣列的copy。
然後我們再來看一下push操作,push是棧特有的方法,此處如果是先進先出的操作,那麼這裡應該就是講元素新增到陣列的第一個位置,然後後面的元素逐個後移,我們來看看push原始碼的實現。
// *** Stack methods ***
/**
* Pushes an element onto the stack represented by this deque. In other
* words, inserts the element at the front of this deque.
*
* <p>This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @throws NullPointerException if the specified element is null
*/
public void push(E e) {
addFirst(e);
}
// The main insertion and extraction methods are addFirst,
// addLast, pollFirst, pollLast. The other methods are defined in
// terms of these.
/**
* Inserts the specified element at the front of this deque.
*
* @param e the element to add
* @throws NullPointerException if the specified element is null
*/
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
這裡倒是有點意思,說明前面的思考確實是不對的,這裡用的是head、tail兩個指標,tail用來往頭部新增元素,head用來往陣列尾部新增元素,如果head == tail則進行擴容。
那再看看陣列的peak操作。
/**
* Retrieves, but does not remove, the head of the queue represented by
* this deque, or returns {@code null} if this deque is empty.
*
* <p>This method is equivalent to {@link #peekFirst}.
*
* @return the head of the queue represented by this deque, or
* {@code null} if this deque is empty
*/
public E peek() {
return peekFirst();
}
public E peekFirst() {
// elements[head] is null if deque empty
return (E) elements[head];
}
peak就是彈出佇列的頭部元素,就是head指標指向的元素,這個比較比較容易理解。
現在再看下poll操作,poll每次操作元素時,會逐個移除佇列頭部元素。
/**
* Retrieves and removes the head of the queue represented by this deque
* (in other words, the first element of this deque), or returns
* {@code null} if this deque is empty.
*
* <p>This method is equivalent to {@link #pollFirst}.
*
* @return the head of the queue represented by this deque, or
* {@code null} if this deque is empty
*/
public E poll() {
return pollFirst();
}
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
這裡就是將頭部的元素取出並返回,然後將頭部的元素置為null,然後head值加一。
再來看看棧的pop操作,想必和poll類似。
/**
* Pops an element from the stack represented by this deque. In other
* words, removes and returns the first element of this deque.
*
* <p>This method is equivalent to {@link #removeFirst()}.
*
* @return the element at the front of this deque (which is the top
* of the stack represented by this deque)
* @throws NoSuchElementException {@inheritDoc}
*/
public E pop() {
return removeFirst();
}
/**
* @throws NoSuchElementException {@inheritDoc}
*/
public E removeFirst() {
E x = pollFirst();
if (x == null)
throw new NoSuchElementException();
return x;
}
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
pop的操作和poll方法實現是相同的,其實這個可以理解,因為queue和stack各自分別是先進先出與先進後出的模式,所以取資料都是一樣的。
3. 綜上
雙端佇列作為Queue和Stack的雙重實現,但是在使用的時候只能選擇一種使用,不能Queue與Stack的api同時使用。