ArrayDeque雙端佇列 使用&實現原理分析
學習Okhttp實現原始碼時,發現其任務分發時用到了ArrayDeque
。因此瞭解一下ArrayDeque
的使用方式
和實現原理
。
一、Deque
deque(double-ended queue
)雙端佇列,是一種具有佇列和棧的性質的資料結構。
雙端佇列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。假設兩端分別為端點A和端點B,在實際應用中:
- 可以有輸出受限的雙端佇列(即端點A允許插入和刪除,端點B只允許插入的雙端佇列);
- 可以有輸入受限的雙端佇列(即端點A允許插入和刪除,端點B只允許刪除的雙端佇列);
- 如果限定雙端佇列從某個端點插入的元素只能從該端點刪除,則該雙端佇列就蛻變為兩個棧底相鄰的棧;
deque(double-ended queue
)也是一種佇列,瞭解deque
時首先需要回憶一下queue
的使用和實現原理。
對於Queue
(先進先出)的詳細使用方式和實現原理,可參考文章:
BlockingQueue使用方式 及 原始碼閱讀
https://blog.csdn.net/xiaxl/article/details/80774479
Deque實現了以下功能方法,供開發者使用:
// 原始碼來自:android-29/java/util/Deque
public interface Deque<E> extends java.util.Queue<E> {
/**
* 新增元素
*/
// 在佇列前邊 新增元素,返回是否新增成功
boolean offerFirst( E e);
// 在佇列後邊 新增元素,返回是否新增成功
boolean offerLast( E e);
// 在佇列前邊 新增元素
void addFirst(E e);
// 在佇列後邊新增元素
void addLast( E e);
/**
* 刪除元素
*/
// 刪除第一個元素,返回刪除元素的值;如果元素為null,將返回null;
E pollFirst();
// 刪除最後一個元素,返回刪除元素的值;如果為null,將返回null;
E pollLast();
// 刪除第一個元素,返回刪除元素的值;如果元素為null,將丟擲異常;
E removeFirst();
// 刪除最後一個元素,返回刪除元素的值;如果為null,將丟擲異常;
E removeLast();
// 刪除第一次出現的指定元素
boolean removeFirstOccurrence( java.lang.Object o);
// 刪除最後一次出現的指定元素
boolean removeLastOccurrence( java.lang.Object o);
/**
* 取資料
*/
// 獲取第一個元素,沒有返回null;
E peekFirst();
// 獲取最後一個元素,沒有返回null;
E peekLast();
// 獲取第一個元素,如果沒有則丟擲異常;
E getFirst();
// 獲取最後一個元素,如果沒有則丟擲異常;
E getLast();
/**
* 佇列方法------------------------------------------
*/
// 向佇列中新增一個元素。若新增成功則返回true;若因為容量限制新增失敗則返回false是。
boolean offer(E e);
// 刪除佇列頭的元素,如果佇列為空,則返回null;
E poll();
// 返回佇列頭的元素,如果佇列為空,則返回null;
E peek();
// 向佇列中新增一個元素。若新增成功則返回true;若因為容量限制新增失敗,則丟擲IllegalStateException異常。
boolean add(E e);
// 刪除佇列頭的元素,如果佇列為空,則丟擲異常;
E remove();
// 返回佇列頭的元素,如果佇列為空,將拋異常;
E element();
/**
* 堆疊方法------------------------------------------
*/
// 棧頂新增一個元素
void push( E e);
// 移除棧頂元素,如果棧頂沒有元素將丟擲異常
E pop();
}
二、ArrayDeque 原始碼學習
2.1、變數說明
// 原始碼來自:android-29/java/util/ArrayDeque
// 用陣列存放佇列元素;
transient Object[] elements;
// 頭部index
transient int head;
// 下一個要新增到尾步的index (除tail=0時,當前的尾部為tail-1);
transient int tail;
head 與 tail 對應的index位置示例如下圖所示:
- addFirst 方法:在佇列前面 新增元素;
程式碼實現中:從陣列的末尾向前新增資料
,如上圖新增順序為 E1、E2、...; - addLast方法:在佇列後面 新增元素;
程式碼實現中:在陣列的前邊向後新增資料
,如上圖新增順序為 Ea、Eb、...; - head 為當前頭部的index;
- tail 為下一個要新增的尾部的index;
2.2、構造方法
// 原始碼來自:android-29/java/util/ArrayDeque
// 構造方法:預設陣列長度為8
public ArrayDeque() {
// 建立一個長度為16的陣列
elements = new Object[16];
}
// 構造方法:根據自定義長度 分配陣列空間
public ArrayDeque(int numElements) {
// 根據輸入長度 分配陣列空間
allocateElements(numElements);
}
// 找到大於需要長度的最小的2的冪整數
private void allocateElements(int numElements) {
// MIN_INITIAL_CAPACITY為8
int initialCapacity = MIN_INITIAL_CAPACITY;
// 假設使用者輸入的為9
if (numElements >= initialCapacity) {
// initialCapacity = 9;
initialCapacity = numElements;
// initialCapacity = 9 | ( 9 >>> 1)
// initialCapacity = ( 1001 ) | ( 0100 ) = 1101 = 13;
initialCapacity |= (initialCapacity >>> 1);
// initialCapacity = 13 | ( 13 >>> 2)
// initialCapacity = ( 1101 ) | ( 0011 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 2);
// initialCapacity = 15 | ( 15 >>> 4)
// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 4);
// initialCapacity = 15 | ( 15 >>> 8)
// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
// 15+1 = 16;
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1; // Good luck allocating 2^30 elements
}
// 建立陣列(陣列的長度為:大於需要長度的最小的2的冪整數)
elements = new Object[initialCapacity];
}
以上兩個構造方法實現中:
- 第一個構造方法,建立一個預設長度為8的陣列;
- 第二個構造方法,如程式碼中註釋舉例,其會通過allocateElements(numElements)將陣列的長度定義為2的倍數(
找到大於需要長度的最小的2的冪整數
); allocateElements(numElements)
方法:可以將一個任意的初始值轉化為2^n的值。
如果本身傳進來的值就是2^n 的值,那麼經過轉化會變成2^(n+1);
如果傳入的值大於等於2^30,那麼經過轉化會變成負值,即< 0,此時會把初始值設定為2^30, 即最大的容量只有2^30;
2.3、佇列首部新增元素
offerFirst(E e)、addFirst(E e) 原始碼實現
// 原始碼來自:android-29/java/util/ArrayDeque
// 在佇列前邊 新增元素,返回是否新增成功
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 在佇列前邊 新增元素
public void addFirst(E e) {
// 存入空資料時,丟擲異常NullPointerException
if (e == null)
throw new NullPointerException();
// 這樣寫 當head=0時,新增到的位置為elements.length - 1
// 從陣列的末尾 向前 依次新增資料
elements[head = (head - 1) & (elements.length - 1)] = e;
// 空間不足
if (head == tail)
doubleCapacity(); // 擴容
}
offerFirst(E e)、addFirst(E e) 在陣列前面新增元素:原始碼實現中,從陣列的末尾向前依次新增資料元素
;
2.4、佇列首部刪除元素
pollFirst()、removeFirst()原始碼實現
// 原始碼來自:android-29/java/util/ArrayDeque
// 刪除第一個元素,返回刪除元素的值;如果元素為null,將丟擲異常;
public E removeFirst() {
E x = pollFirst();
// 若為空,則丟擲異常;
if (x == null)
throw new NoSuchElementException();
return x;
}
// 刪除第一個元素,返回刪除元素的值;如果元素為null,將返回null;
public E pollFirst() {
// 陣列
final Object[] elements = this.elements;
// 頭部index
final int h = head;
// 頭部元素
E result = (E) elements[h];
// 如果頭部元素為null,則返回null
if (result != null) {
// 資料元素出佇列
elements[h] = null; // Must null out slot
// 指向下一個元素
head = (h + 1) & (elements.length - 1);
}
return result;
}
執行pollFirst()、removeFirst()方法,進行資料出佇列時,其資料的刪除示意圖如下圖所示:
2.5、佇列尾部新增元素
offerLast(E e)、addLast(E e) 原始碼實現
// 原始碼來自:android-29/java/util/ArrayDeque
// 在陣列後新增元素,返回是否新增成功
public boolean offerLast(E e) {
addLast(e);
return true;
}
// 在陣列後面新增元素
public void addLast(E e) {
// 存入空資料時,丟擲異常NullPointerException
if (e == null)
throw new NullPointerException();
// 存入資料
elements[tail] = e;
// (tail + 1) == head 空間已滿
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
// 擴容
doubleCapacity();
}
offerLast(E e)、addLast(E e) 在陣列後新增元素: 原始碼實現中,從陣列的前端向後依次新增資料元素
;
2.6、佇列尾部移除元素
pollLast()、removeLast()原始碼實現
// 原始碼來自:android-29/java/util/ArrayDeque
// 刪除最後一個元素,返回刪除元素的值;如果為null,將丟擲異常;
public E removeLast() {
E x = pollLast();
if (x == null)
throw new NoSuchElementException();
return x;
}
// 刪除最後一個元素,返回刪除元素的值;如果為null,返回null;
public E pollLast() {
// 陣列元素
final Object[] elements = this.elements;
// tail-1
final int t = (tail - 1) & (elements.length - 1);
// 獲取當前元素
E result = (E) elements[t];
if (result != null) {
// 當前元素置空
elements[t] = null;
// 將tail-1 賦值給 tail
tail = t;
}
return result;
}
執行pollLast()、removeLast()方法,進行資料出佇列時,其資料的刪除示意圖如下圖所示:
2.7、擴容 doubleCapacity()
向陣列中新增元素時,若陣列容量不足,會呼叫doubleCapacity()方法,擴容為當前容量的兩倍:
// 原始碼來自:android-29/java/util/ArrayDeque
// 空間不足:擴容
private void doubleCapacity() {
assert head == tail;
// 頭部 index
int p = head;
// 陣列長度
int n = elements.length;
//
int r = n - p;
// 容量擴充套件為當前的2倍
int newCapacity = n << 1;
// 新的容量超過2^30,丟擲異常
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
// 建立一個新的陣列
Object[] a = new Object[newCapacity];
// 拷貝原陣列從 head位置到結束的資料
System.arraycopy(elements, p, a, 0, r);
// 拷貝原陣列從 開始到head的資料
System.arraycopy(elements, 0, a, r, p);
// elements置空,保證其中元素可被垃圾回收
Arrays.fill(elements, null);
// elements被重新賦值
elements = a;
// header 和tail重新賦值
head = 0;
tail = n;
}
擴容後的陣列為原始陣列空間的兩倍:
三、ArrayDeque 佇列應用
3.1 入佇列
// 向佇列中新增一個元素。若新增成功則返回true;若因為容量限制新增失敗則返回false是。
boolean offer(E e);
ArrayDeque入佇列時:offer
會呼叫offerLast
實現入佇列
3.2 出佇列
// 刪除佇列頭的元素,如果佇列為空,則返回null;
E poll();
ArrayDeque出佇列時:poll
會呼叫pollFirst
實現出佇列
四、ArrayDeque 堆疊應用
4.1 入堆疊
// 棧頂新增一個元素
void push( E e);
ArrayDeque入堆疊時:push
會呼叫offerLast
實現入堆疊
4.2 出堆疊
// 移除棧頂元素,如果棧頂沒有元素將丟擲異常
E pop();
ArrayDeque出堆疊時:poll
會呼叫pollFirst
實現出堆疊
參考
百度百科deque
https://baike.baidu.com/item/deque/849385?fr=aladdin