Java中定义了java.util.Queue
接口用来表示队列。Java中的Queue
与List
、Set
属于同一个级别接口,它们都是继承于Collection
接口。
Java中还定义了一种双端队列java.util.Deque
,我们常用的LinkedList
就是实现了Deque
接口。
从上图我们可以得知,Queue有一个直接子类PriorityQueue,而Deque中直接子类有两个:LinkedList以及ArrayDeque。
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// *** Stack methods ***
void push(E e);
E pop();
// *** Collection methods ***
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。而我们还能看到,ArrayDeque作为队列时的效率比LinkedList要高,而在栈的使用场景下,无疑具有尾结点不需判空的LinkedList较高效。
PriorityQueue可以作为堆使用,而且可以根据传入的Comparator实现大小的调整,会是一个很好的选择。
ArrayDeque通常作为栈或队列使用,但是栈的效率不如LinkedList高。
LinkedList通常作为栈或队列使用,但是队列的效率不如ArrayQueue高。
我们知道,Queue
是队列,只能一头进,另一头出。
如果把条件放松一下,允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),学名Deque
。
Java集合提供了接口Deque
来实现一个双端队列,它的功能是:
- 既可以添加到队尾,也可以添加到队首;
- 既可以从队首获取,又可以从队尾获取。
我们来比较一下Queue
和Deque
出队和入队的方法:
Queue | Deque | |
---|---|---|
添加元素到队尾 | add(E e) / offer(E e) | addLast(E e) / offerLast(E e) |
取队首元素并删除 | E remove() / E poll() | E removeFirst() / E pollFirst() |
取队首元素但不删除 | E element() / E peek() | E getFirst() / E peekFirst() |
添加元素到队首 | 无 | addFirst(E e) / offerFirst(E e) |
取队尾元素并删除 | 无 | E removeLast() / E pollLast() |
取队尾元素但不删除 | 无 | E getLast() / E peekLast() |
对于添加元素到队尾的操作,Queue
提供了add()
/offer()
方法,而Deque
提供了addLast()
/offerLast()
方法。添加元素到对首、取队尾元素的操作在Queue
中不存在,在Deque
中由addFirst()
/removeLast()
等方法提供。
注意到Deque
接口实际上扩展自Queue
:
public interface Deque<E> extends Queue<E> {
...
}
因此,Queue
提供的add()
/offer()
方法在Deque
中也可以使用,但是,使用Deque
,最好不要调用offer()
,而是调用offerLast()
:
import java.util.Deque;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
deque.offerLast("A"); // A
deque.offerLast("B"); // A <- B
deque.offerFirst("C"); // C <- A <- B
System.out.println(deque.pollFirst()); // C, 剩下A <- B
System.out.println(deque.pollLast()); // B, 剩下A
System.out.println(deque.pollFirst()); // A
System.out.println(deque.pollFirst()); // null
}
}
运行结果
C
B
A
null
如果直接写deque.offer()
,我们就需要思考,offer()
实际上是offerLast()
,我们明确地写上offerLast()
,不需要思考就能一眼看出这是添加到队尾。
因此,使用Deque
,推荐总是明确调用offerLast()
/offerFirst()
或者pollFirst()
/pollLast()
方法。
Deque
是一个接口,它的实现类有ArrayDeque
和LinkedList
。
我们发现LinkedList
真是一个全能选手,它即是List
,又是Queue
,还是Deque
。但是我们在使用的时候,总是用特定的接口来引用它,这是因为持有接口说明代码的抽象层次更高,而且接口本身定义的方法代表了特定的用途。
// 不推荐的写法:
LinkedList<String> d1 = new LinkedList<>();
d1.offerLast("z");
// 推荐的写法:
Deque<String> d2 = new LinkedList<>();
d2.offerLast("z");
可见面向抽象编程的一个原则就是:尽量持有接口,而不是具体的实现类。
双端队列Deque(double ended queue)
Deque que = new LinkedList();
栈式操作:
push(e)
pop()//弹出栈顶
peek()//查看栈顶
下面是Java中Queue的一些常用方法:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
小结
Deque
实现了一个双端队列(Double Ended Queue),它可以:
- 将元素添加到队尾或队首:
addLast()
/offerLast()
/addFirst()
/offerFirst()
; - 从队首/队尾获取元素并删除:
removeFirst()
/pollFirst()
/removeLast()
/pollLast()
; - 从队首/队尾获取元素但不删除:
getFirst()
/peekFirst()
/getLast()
/peekLast()
; - 总是调用
xxxFirst()
/xxxLast()
以便与Queue
的方法区分开; - 避免把
null
添加到队列。