文章目录
一.Java集合架构图
点击放大查看
二.什么是Queue?
-
队列(Queue)
是一种经常使用的集合。Queue实际上是实现了一个先进先出(FIFO:First In First Out)
的有序列表(有序集合)
。 -
队列List的区别在于,List可以在
任意位置添加和删除元素
-
Queue只有两个操作:
把元素添加到队列末尾;
从队列头部取出元素。
-
例如:超市的收银台就是一个队列:
排到前面的先买单,排到后面的后买单
三.Java中Queue提供的标准方法
在Java的标准库
中,队列接口Queue定义了以下几个方法:
方法 | 方法描述 |
---|---|
int size() | 获取队列长度 |
boolean add(E) / boolean offer(E) | 添加元素到队尾 |
E remove() / E poll() | 获取队首元素并从队列中删除 |
E element() / E peek() | 获取队首元素但并不从队列中删除 |
- 对于具体的实现类,
有的Queue有最大队列长度限制,有的Queue没有
。 - 注意到
添加
、删除
和获取队列元素
总是有两个方法, 这是因为在添加或获取元素失败时,这两个方法的行为是不同的。 我们用一个表格总结如下
throw Exception | 返回false或null | |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除(获取第一个元素) | E element() | E peek() |
-
举个栗子,假设我们有一个队列
-
当调用
add()
方法添加失败
时(可能超过了队列的容量
)会抛出异常
-
当调用
offer()
方法当添加失败时,不会抛异常,而是返回false
-
当调用
remove()
中取出队首元素
时,如果是一个空队列,调用remove()方法,它会抛出异常
-
当调用
poll()
方法来取出队首元素
,当获取失败时,它不会抛异常,而是返回null
-
当调用poll(),会获取队首元素,并且获取到的元素已经从队列中被删除
-
当调用peek(),会获取队首元素时,并不会从队列中删除这个元素,所以可以反复获取的都是第一个元素
注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。
四.Queue小结
队列Queue实现了一个先进先出(FIFO)
的数据结构:
- 通过
add()/offer()
方法将元素添加到队尾; - 通过
remove()/poll()
从队首获取元素并删除; - 通过
element()/peek()
从队首获取元素但不删除。 要避免把null添加到队列
。
五.优先队列PriorityQueue
1.什么是优先队列
Queue是一个先进先出(FIFO)的队列。
-
在银行柜台办业务时,我们假设只有一个柜台在办理业务,但是办理业务的人很多,怎么办?
- 可以每个人先取一个号,如:
A1、A2、A3……
然后,按照号码顺序依次办理
,实际上这就是一个Queue
。 - 如果这时来了一个VIP客户,他的号码是
V1
,虽然当前排队的是A10、A11、A12……
但是柜台下一个呼叫的客户号码却是V1
。 - 这个时候,我们发现,要实现
“VIP插队”
的业务,用Queue
就不行了,因为Queue会严格按FIFO的原则取出队首元素。 我们需要的是优先队列
:PriorityQueue。
- 可以每个人先取一个号,如:
PriorityQueue和Queue的区别在于 :它的 出队顺序
与元素的优先级
有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
- 要使用PriorityQueue,我们就必须给
每个元素定义“优先级”
。
2. Java中使用优先队列
- 放入PriorityQueue的对象,必须实现
比较器Comparator接口
,PriorityQueue会根据元素的排序顺序
决定出队的优先级
。
public class Main {
public static void main(String[] args) {
Queue<String> queue = new PriorityQueue<>();
// 添加3个元素到队列:
queue.offer("apple");
queue.offer("pear");
queue.offer("banana");
System.out.println(queue.poll()); // apple
System.out.println(queue.poll()); // banana
System.out.println(queue.poll()); // pear
System.out.println(queue.poll()); // null,因为队列为空
}
}
- 上面代码存入的顺序是
"apple"、"pear"、"banana"
,取出的顺序却是"apple"、"banana"、"pear"
, 这是因为从字符串的排序看,"apple"排在最前面,"pear"排在最后面。 - 说明PriorityQueue默认是根据
Comparator比较器
进行自然排序
的
如果存放的对象没有使用Comparable接口
PriorityQueue
允许我们提供一个Comparator对象
来判断两个元素的顺序
。
以银行排队业务为例,实现一个PriorityQueue:
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
/**
* @Description TODO
* @Author JianPeng OuYang
* @Date 2020/2/1 15:40
* @Version v1.0
*/
public class TestPriorityQueue {
public static void main(String[] args) {
Queue<User> queue = new PriorityQueue<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//如果首字符相等,按自然顺序排序
if (o1.number.charAt(0) == o2.number.charAt(0)) {
// 如果两人的号都是A开头或者都是V开头,比较号的大小:
return o1.number.compareTo(o2.number);
}
//如果比较对象首字母不相等,且首字母为 “V”,保持顺序
if (o1.number.charAt(0) == 'V') {
// u1的号码是V开头,优先级高:
return -1;
//如果比较对象首字母不相等,且首字母不为 “V”,交换顺序
} else {
return 1;
}
}
});
// 添加3个元素到队列:
queue.offer(new User("Bob", "A2"));
queue.offer(new User("Alice", "A10"));
queue.offer(new User("Alice", "A1"));
queue.offer(new User("Boss", "V1"));
System.out.println(queue.poll()); // Boss/V1
System.out.println(queue.poll()); // Bob/A1
System.out.println(queue.poll()); // Alice/A2
System.out.println(queue.poll()); // null,因为队列为空
}
}
class User {
public String name;
public String number;
public User(String name, String number) {
this.name = name;
this.number = number;
}
@Override
public String toString() {
return name + "/" + number;
}
}
3. ProrityQueue小结
-
PriorityQueue实现了一个优先队列:
从队首获取元素时,总是获取优先级最高的元素。
-
PriorityQueue
默认按元素比较的自然顺序排序
(必须实现Comparator接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
六.双端队列Deque
1.什么是双端队列
- Queue是队列,只能一头进,另一头出。
- Deque 叫
双端队列(Double Ended Queue)
,允许两头都进,两头都出。
Java集合提供了接口Deque来实现一个双端队列,它的功能是:
既可以添加到队尾,也可以添加到队首
既可以从队首获取,又可以从队尾获取
- Deque实际上也是
继承自Queue
,基于Queue进行拓展的public interface Deque<E> extends Queue<E> {}
2.对比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()
等方法提供。 -
Queue提供的add()/offer()方法在Deque中也可以使用,但使用Deque,最好不要调用offer(),而是调用offerLast()
3.Java中使用Deque
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 //取队尾元素并删除
System.out.println(deque.pollFirst()); // A //取队首元素并删除
System.out.println(deque.pollFirst()); // null
}
}
使用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");
4.ArrayDeque
-
ArrayDeque是基于可变数组实现的,且 没有容量限制,可根据需求自动进行扩容的
非线程安全
的双端队列。该类性能可能快于stack
,在作为队列时快于LinkedList
,ArrayDeque作为stack的取代类,而使用。-
ArrayDeque 不允许空元素 null
-
当用作栈
stack
时,比 Stack 快,当用作队列Queue
时,比 LinkedList 快
一是因为它内部结构是一个循环数组(只需操作索引),二是没有使用 Synchronized 修饰方法(非线程安全
) -
扩容机制为:扩容成原来的两倍,然后将原来的内容复制到新数组中
-
容量限制:容量必须为2的幂次方,最小为8,默认为16.
-
应用场景:在很多场景下可以用来代替LinkedList,ArrayDeque 为双端队列,支持首部,尾部两端的操作,因此做双端操作可用于fifo等Queue, 做单端操作可做为Stack.
-
5.Deque总结
Deque
实现了一个双端队列(Double Ended Queue)
,它可以:
- 将元素添加到队尾或队首:
addLast()/offerLast()/addFirst()/offerFirst()
- 从队首/队尾获取元素并删除:
removeFirst()/pollFirst()/removeLast()/pollLast()
- 从队首/队尾获取元素但不删除:
getFirst()/peekFirst()/getLast()/peekLast()
- 总是调用
xxxFirst()/xxxLast()
以便与Queue
的方法区分开;
避免把null添加到队列。