Java 中的 Deque(雙端隊列)是一種具有隊列和棧特性的數據結構,它允許在兩端進行插入和刪除操作。Deque 接口是 Java 集合框架中的一部分,它定義了雙端隊列的基本操作。
BlockingDeque 接口: BlockingDeque 接口是 Deque 接口的子接口,它表示一個支持阻塞操作的雙端隊列。它定義了一些阻塞方法,如 putFirst(), putLast(), takeFirst(), takeLast() 等,這些方法在隊列滿或空時會阻塞線程。
LinkedBlockingDeque 類: LinkedBlockingDeque 類是 BlockingDeque 接口的一個實現,它基于鏈表實現了一個無界的阻塞雙端隊列。它支持并發訪問,并提供了阻塞的插入和刪除操作。由于它是無界隊列,因此可以根據需要添加任意數量的元素。
ArrayDeque 類: ArrayDeque 類是 Deque 接口的一個實現,它基于數組實現了一個雙端隊列。它支持并發訪問,并提供了高效的插入和刪除操作。ArrayDeque 是一個動態調整大小的數組,可以根據需要自動增加或縮小容量。
Deque
Deque 接口: Deque 接口是 Java 集合框架中雙端隊列的根接口。它擴展了 Queue 接口,并添加了從兩端操作隊列的方法,如 addFirst(), addLast(), removeFirst(), removeLast() 等。
- addFirst(E e):在雙端隊列的開頭插入指定元素。
- addLast(E e):在雙端隊列的末尾插入指定元素。
- removeFirst():移除并返回雙端隊列的第一個元素。
- removeLast():移除并返回雙端隊列的最后一個元素。
- getFirst():獲取雙端隊列的第一個元素,但不移除。
- getLast():獲取雙端隊列的最后一個元素,但不移除。
- peekFirst():獲取雙端隊列的第一個元素,如果隊列為空則返回 null。
- peekLast():獲取雙端隊列的最后一個元素,如果隊列為空則返回 null。
…
Deque 接口有多個實現類,常用的有:
- ArrayDeque:基于數組實現的雙端隊列,它是一個動態調整大小的數組,可以根據需要自動增加或縮小容量。
- LinkedList:基于鏈表實現的雙端隊列,它提供了高效的插入和刪除操作,但訪問元素的性能較低。
- LinkedBlockingDeque:基于鏈表實現的無界阻塞雙端隊列,它支持并發訪問,并提供了阻塞的插入和刪除操作。
import java.util.Deque;
import java.util.ArrayDeque;public class Main {public static void main(String[] args) {Deque<Integer> deque = new ArrayDeque<>();deque.addFirst(1);deque.addLast(2);deque.addFirst(3);System.out.println(deque); // 輸出 [3, 1, 2]int first = deque.removeFirst();System.out.println(first); // 輸出 3int last = deque.removeLast();System.out.println(last); // 輸出 2int peekFirst = deque.peekFirst();System.out.println(peekFirst); // 輸出 1int peekLast = deque.peekLast();System.out.println(peekLast); // 輸出 1System.out.println(deque); // 輸出 [1]}
}
我們創建了一個 ArrayDeque
對象,并使用 addFirst()
和 addLast()
方法在雙端隊列的開頭和末尾插入元素。然后使用 removeFirst()
和 removeLast()
方法移除元素。使用 peekFirst()
和 peekLast()
方法獲取元素但不移除。最后輸出雙端隊列的內容。
BlockingDeque
他是Java集合中的一個接口,是Deque的子接口,表示一個支持阻塞操作的雙向隊列。BlockingDeque 繼承了 Deque 接口的所有方法,并添加了一些阻塞方法,這些方法在隊列滿或空時會阻塞線程,直到隊列可用或有元素可用時才會繼續執行。
- putFirst(E e):將指定元素插入到雙端隊列的開頭,如果隊列已滿,則阻塞當前線程直到隊列可用。
- putLast(E e):將指定元素插入到雙端隊列的末尾,如果隊列已滿,則阻塞當前線程直到隊列可用。
- takeFirst():移除并返回雙端隊列的第一個元素,如果隊列為空,則阻塞當前線程直到有元素可用。
- takeLast():移除并返回雙端隊列的最后一個元素,如果隊列為空,則阻塞當前線程直到有元素可。
- offerFirst(E e, long timeout, TimeUnit unit):將指定元素插入到雙端隊列的開頭,如果隊列已滿,則阻塞當前線程一段時間,直到超時或隊列可用。
- offerLast(E e, long timeout, TimeUnit unit):將指定元素插入到雙端隊列的末尾,如果隊列已滿,則阻塞當前線程一段時間,直到超時或隊列可用。
- pollFirst(long timeout, TimeUnit unit):移除并返回雙端隊列的第一個元素,如果隊列為空,則阻塞當前線程一段時間,直到超時或有元素可用。
- pollLast(long timeout, TimeUnit unit):移除并返回雙端隊列的最后一個元素,如果隊列為空,則阻塞當前線程一段時間,直到超時或有元素可用。
其他方法:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;public class Main {public static void main(String[] args) {BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(2);try {blockingDeque.putFirst(1);blockingDeque.putLast(2);System.out.println(blockingDeque); // 輸出 [1, 2]blockingDeque.putFirst(3); // 阻塞當前線程,直到隊列可用blockingDeque.putLast(4); // 阻塞當前線程,直到隊列可用} catch (InterruptedException e) {e.printStackTrace();}}
}
創建了一個容量為 2 的 LinkedBlockingDeque 對象,并使用 putFirst() 和 putLast() 方法向雙端隊列中插入元素。當隊列已滿時,putFirst() 和 putLast() 方法會阻塞當前線程,直到隊列可用。
由于我們的隊列容量為 2,并且已經插入了兩個元素,所以在插入第三個和第四個元素時,當前線程會被阻塞,直到隊列中有空間可用。
LinkedBlockingDeque
LinkedBlockingDeque是Java中的一個雙向鏈表阻塞隊列,它實現了BlockingQueue接口,可以用于實現生產者-消費者模式等多線程場景。在本篇博客中,我將詳細介紹LinkedBlockingDeque的特性、用法以及示例代碼。
LinkedBlockingDeque的常用方法
- add(E e):將指定元素添加到隊列的尾部,如果隊列已滿,則拋出IllegalStateException異常。
- offer(E e):將指定元素添加到隊列的尾部,如果隊列已滿,則返回false。
- put(E e):將指定元素添加到隊列的尾部,如果隊列已滿,則阻塞直到隊列有空間可用。
- offerFirst(E e):將指定元素添加到隊列的頭部,如果隊列已滿,則返回false。
- offerLast(E e):將指定元素添加到隊列的尾部,如果隊列已滿,則返回false。
- poll():獲取并移除隊列頭部的元素,如果隊列為空,則返回null。
- poll(long timeout, TimeUnit unit):在指定的時間內等待,獲取并移除隊列頭部的元素,如果超時仍未有元素可用,則返回null。
- take():獲取并移除隊列頭部的元素,如果隊列為空,則阻塞直到隊列非空。
- pollFirst():獲取并移除隊列頭部的元素,如果隊列為空,則返回null。
- pollLast():獲取并移除隊列尾部的元素,如果隊列為空,則返回null。
- peek():獲取但不移除隊列頭部的元素,如果隊列為空,則返回null。
- peekFirst():獲取但不移除隊列頭部的元素,如果隊列為空,則返回null。
- peekLast():獲取但不移除隊列尾部的元素,如果隊列為空,則返回null。
- remove(Object o):從隊列中移除指定元素,如果存在該元素則返回true,否則返回false。
- contains(Object o):判斷隊列是否包含指定元素,如果包含則返回true,否則返回false。
其他方法:
LinkedBlockingDeque的特性
- 雙向鏈表結構:LinkedBlockingDeque內部采用雙向鏈表實現,可以高效地支持在隊列兩端的插入和刪除操作。
- 阻塞操作:當隊列為空時,從隊列中獲取元素的操作會被阻塞,直到隊列中有元素可用;當隊列已滿時,向隊列中添加元素的操作也會被阻塞,直到隊列有空間可用。
- 無界隊列:LinkedBlockingDeque在構造時可以選擇是否設置隊列的容量限制,如果不設置容量限制,則隊列大小理論上可以無限增長。
LinkedBlockingDeque的使用
- 創建LinkedBlockingDeque對象
LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<>();
- 添加元素到隊列
deque.add("Element 1"); // 添加元素到隊列尾部
deque.offerFirst("Element 2"); // 添加元素到隊列頭部
- 獲取并移除隊列元素
String element = deque.poll(); // 獲取并移除隊列頭部元素
String lastElement = deque.pollLast(); // 獲取并移除隊列尾部元素
- 獲取但不移除隊列元素
String peekElement = deque.peek(); // 獲取但不移除隊列頭部元素
String peekLastElement = deque.peekLast(); // 獲取但不移除隊列尾部元素
- 阻塞操作
String element = deque.take(); // 阻塞直到隊列非空,然后獲取并移除隊列頭部元素
deque.put("Element 3"); // 阻塞直到隊列有空間可用,然后添加元素到隊列尾部
示例代碼
下面是一個簡單的生產者-消費者示例,使用LinkedBlockingDeque作為共享隊列:
import java.util.concurrent.LinkedBlockingDeque;public class ProducerConsumerExample {private static LinkedBlockingDeque<Integer> queue = new LinkedBlockingDeque<>();public static void main(String[] args) {Thread producer = new Thread(() -> {try {for (int i = 0; i < 10; i++) {queue.put(i);System.out.println("Produced: " + i);Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}});Thread consumer = new Thread(() -> {try {for (int i = 0; i < 10; i++) {int num = queue.take();System.out.println("Consumed: " + num);}} catch (InterruptedException e) {e.printStackTrace();}});producer.start();consumer.start();}
}
LinkedBlockingDeque是一個功能強大的雙向鏈表阻塞隊列,適用于多線程生產者-消費者模式等場景。通過阻塞操作和雙向鏈表結構,它能夠高效地支持并發操作。在實際應用中,可以根據具體需求選擇合適的隊列實現來提升系統性能和可靠性。
ArrayDeque
ArrayDeque的特性
- 數組實現:ArrayDeque內部使用數組來存儲元素,可以高效地支持在隊列兩端的插入和刪除操作。
- 雙端隊列:ArrayDeque既可以作為隊列使用,也可以作為棧使用。它支持在隊列的頭部和尾部進行元素的插入和刪除,因此可以靈活地滿足不同的需求。
- 無界隊列:ArrayDeque在構造時不需要指定容量大小,它的大小會根據實際元素的數量自動調整,理論上可以無限增長。
- 非線程安全:ArrayDeque不是線程安全的,如果需要在多線程環境下使用,需要進行適當的同步處理。
注意
-
非線程安全:ArrayDeque不是線程安全的,如果需要在多線程環境下使用,需要進行適當的同步處理。可以使用
Collections.synchronizedDeque()
方法創建一個線程安全的ArrayDeque。 -
不支持null元素:ArrayDeque不支持存儲null元素,如果嘗試添加null元素,會拋出
NullPointerException
異常。 -
避免頻繁調整容量:ArrayDeque的內部實現是通過循環數組來存儲元素,當隊列的容量不足時,會自動進行擴容。在頻繁添加或刪除元素的場景下,可能會導致多次擴容,影響性能。如果事先能夠大致估計隊列的大小,可以通過構造函數
ArrayDeque(int numElements)
來指定初始容量,避免頻繁的容量調整。 -
適用于頻繁操作兩端的場景:ArrayDeque的優勢在于支持高效地在隊列的兩端進行插入和刪除操作。如果只需要在隊列的一端進行操作,建議使用LinkedList或ArrayBlockingQueue等其他隊列實現。
-
注意棧操作的順序:如果將ArrayDeque用作棧使用,需要注意棧操作的順序。使用
push()
方法將元素添加到棧頂,使用pop()
方法移除并返回棧頂元素。棧操作需要保持一致的順序,否則可能導致元素順序的混亂。 -
避免過度依賴索引操作:由于ArrayDeque是基于數組實現的,它支持通過索引來訪問元素。但是,在頻繁插入和刪除元素的場景下,使用索引操作可能會導致性能下降。因此,在使用ArrayDeque時,應盡量避免過度依賴索引操作,而是使用隊列提供的方法來操作元素。
ArrayDeque的使用
- 創建ArrayDeque對象
ArrayDeque<String> deque = new ArrayDeque<>();
- 添加元素到隊列
deque.add("Element 1"); // 添加元素到隊列尾部
deque.offerFirst("Element 2"); // 添加元素到隊列頭部
- 獲取并移除隊列元素
String element = deque.poll(); // 獲取并移除隊列頭部元素
String lastElement = deque.pollLast(); // 獲取并移除隊列尾部元素
- 獲取但不移除隊列元素
String peekElement = deque.peek(); // 獲取但不移除隊列頭部元素
String peekLastElement = deque.peekLast(); // 獲取但不移除隊列尾部元素
- 棧操作
deque.push("Element 3"); // 將元素添加到棧頂
String popElement = deque.pop(); // 移除并返回棧頂元素
示例代碼
import java.util.ArrayDeque;public class ArrayDequeExample {public static void main(String[] args) {ArrayDeque<String> queue = new ArrayDeque<>();queue.offer("Element 1");queue.offer("Element 2");System.out.println(queue.poll()); // 輸出: Element 1ArrayDeque<String> stack = new ArrayDeque<>();stack.push("Element 3");stack.push("Element 4");System.out.println(stack.pop()); // 輸出: Element 4}
}
ArrayDeque是一個非常實用的雙端隊列,可以靈活地支持隊列和棧的操作。通過數組實現和雙端插入和刪除操作,它能夠高效地滿足不同場景下的需求。在實際應用中,可以根據具體需求選擇合適的隊列實現來提升系統性能和可靠性。