核心概念
Deque
(double ended queue,双端队列)和Queue
(队列)都是Java集合框架中的接口,它们用于处理元素的排队和出队,但是它们之间存在一些重要的区别,如下:
1、Queue接口:
Queue
接口代表一个先进先出(FIFO)的队列,只能从一端添加元素,并从另一端移除元素,因此,可以使用add()
、offer()
方法将元素添加到队列的末尾,使用remove()
、poll()
方法从队列的头部移除元素,如果尝试从一个空的队列中移除元素,remove()
方法会抛出NoSuchElementException
,而poll()
方法则会返回null
。
2、Deque接口:
Deque
接口,即双端队列,允许从两端添加或者移除元素,它提供了两套添加和移除元素的方法,一套在队列的头部操作,另一套在队列的尾部操作,因此Deque
就可以当作队列(FIFO)或者栈(LIFO)来使用,对于Deque
,可以在队列头部使用addFirst()
、offerFirst()
添加元素,使用removeFirst()
、pollFirst()
移除元素;在队列尾部使用addLast()
、offerLast()
添加元素,使用removeLast()
、pollLast()
移除元素,如果尝试从一个空的双端队列中移除元素,那么相关的removeXXX()
方法同样会抛出NoSuchElementException
,而pollXXX()
方法会返回null
。
Queue
接口和Deque
接口的主要区别在于,Queue
接口仅支持在一端添加元素,在另一端移除元素,而Deque
接口则支持在两端都进行添加和移除元素的操作,另外,Deque
接口的功能更强大,因为它可以当作队列、栈或者双端队列来使用,而Queue
接口只能当作队列来使用。
代码案例
Deque
Deque接口代表一个双端队列(double-ended queue),双端队列是一个具有队列和栈的性质的数据结构,它允许元素从两端插入和删除,因此可以在队列的前面(头部)或后面(尾部)添加或移除元素,它的主要功能包括:
1、添加元素:
addFirst(E e)
/offerFirst(E e)
:在队列的头部插入元素,如果队列已满,addFirst
会抛出IllegalStateException
,而offerFirst
则返回false
。addLast(E e)
/offerLast(E e)
:在队列的尾部插入元素,如果队列已满,addLast
会抛出IllegalStateException
,而offerLast
则返回false
。
2、移除元素:
removeFirst()
/pollFirst()
:从队列的头部移除并返回元素,如果队列为空,removeFirst
会抛出NoSuchElementException
,而pollFirst
则返回null
。removeLast()
/pollLast()
:从队列的尾部移除并返回元素,如果队列为空,removeLast
会抛出NoSuchElementException
,而pollLast
则返回null
。
3、检查元素
getFirst()
/peekFirst()
:获取但不移除队列头部的元素,如果队列为空,getFirst
会抛出NoSuchElementException
,而peekFirst
则返回null
。getLast()
/peekLast()
:获取但不移除队列尾部的元素,如果队列为空,getLast
会抛出NoSuchElementException
,而peekLast
则返回null
。
Deque接口的使用场景非常广泛,主要包括如下:
- 当需要一个可以作为队列(FIFO)或栈(LIFO)使用的数据结构时。
- 在需要高效地在两端添加或移除元素的场景中,如实现撤销/重做功能、缓冲区管理等。
- 作为其他数据结构的底层实现,如实现一个自定义的栈或队列。
下面是一个简单的代码示例,展示了如何使用Deque接口,如下代码:
import java.util.Deque;
import java.util.LinkedList;
public class DequeExample {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
// 在队列头部添加元素
deque.addFirst("Element 1 (Head)");
// 在队列尾部添加元素
deque.addLast("Element 2 (Tail)");
// 在队列头部继续添加元素
deque.offerFirst("Element 0 (Head)");
// 打印队列元素
System.out.println("Deque contents: " + deque);
// 从队列头部移除并返回元素
String removedElement = deque.pollFirst();
System.out.println("Removed element from head: " + removedElement);
// 从队列尾部移除并返回元素
removedElement = deque.pollLast();
System.out.println("Removed element from tail: " + removedElement);
// 检查队列头部元素而不移除
String headElement = deque.peekFirst();
System.out.println("Head element: " + headElement);
// 检查队列是否为空
System.out.println("Deque is empty? " + deque.isEmpty());
}
}
输出将会是:
Deque contents: [Element 0 (Head), Element 1 (Head), Element 2 (Tail)]
Removed element from head: Element 0 (Head)
Removed element from tail: Element 2 (Tail)
Head element: Element 1 (Head)
Deque is empty? false
在上面代码中,使用了LinkedList
类作为Deque
接口的实现,因为LinkedList
类实现了Deque
接口,因此它提供了双端队列的所有操作,向队列中添加了一些元素,然后从头部和尾部移除它们,并检查了队列的头部元素和是否为空。
Queue
代表一个队列数据结构,即一种特殊的线性表,只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,这种操作规则也被称为“先进先出”(FIFO,First-In-First-Out),它的主要功能包括:
1、插入元素:
add(E e)
:将指定的元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true
,如果当前没有可用的空间,则抛出IllegalStateException
,不过,在Queue
接口的实现中,这个方法通常不会抛出异常,因为大多数队列实现都是有界的,但这个界限通常很大。offer(E e)
:将指定的元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true
,如果当前没有可用的空间,则返回false
。
2、移除元素
remove()
:移除并返回此队列的头部,如果此队列为空,则抛出NoSuchElementException
。poll()
:移除并返回此队列的头部,或返回null
如果此队列为空。
3、检查元素
element()
:检索,但不移除此队列的头部,如果此队列为空,则抛出NoSuchElementException
。peek()
:检索,但不移除此队列的头部,或返回null
如果此队列为空。
使用场景
- 缓冲:当数据需要以有序的方式处理,但又不需要立即处理所有数据时,队列作为缓冲很有用,例如,打印任务队列,网络请求队列等。
- 生产者-消费者问题:队列经常用于协调多个线程之间的合作,尤其是在生产者-消费者场景中,生产者将产品放入队列,而消费者从队列中取出产品进行消费。
- 广度优先搜索:在图论中,队列用于实现广度优先搜索(BFS)算法。
- 事件驱动的系统:队列可以用于存储待处理的事件,例如用户界面事件或系统事件。
代码示例
下面是一个简单的 Queue
接口使用示例,使用了 LinkedList
类作为实现,如下代码:
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// 插入元素
queue.offer("Apple");
queue.offer("Banana");
queue.offer("Cherry");
System.out.println("Initial Queue: " + queue);
// 移除元素
String removedElement = queue.poll();
System.out.println("Removed Element: " + removedElement);
System.out.println("Queue after removal: " + queue);
// 检查元素
String headElement = queue.peek();
System.out.println("Head of the Queue: " + headElement);
// 遍历队列
System.out.println("Iterating over the queue:");
for (String fruit : queue) {
System.out.println(fruit);
}
}
}
输出:
Initial Queue: [Apple, Banana, Cherry]
Removed Element: Apple
Queue after removal: [Banana, Cherry]
Head of the Queue: Banana
Iterating over the queue:
Banana
Cherry
在这个示例中,创建了一个 Queue
,使用 offer
方法插入了几个元素,使用 poll
方法移除了队列的头部元素,并使用 peek
方法来查看当前队列的头部元素,最后,使用增强for循环遍历了队列中的所有元素。
END!