Queue
Queue
队列(
Queue
)是一种经常使用的集合。Queue
实际上是实现了一个先进先出(FIFO:First In First Out)的有序表
Queue
接口常用的实现类是LinkedList
在Java的标准库中,队列接口Queue
定义了以下几个方法:
int size()
:获取队列长度;boolean add(E)
/boolean offer(E)
:添加元素到队尾;E remove()
/E poll()
:获取队首元素并从队列中删除;E element()
/E peek()
:获取队首元素但并不从队列中删除。
add/offer、remove/poll、element/peek的区别:
在添加或获取元素失败时,这两个方法的行为是不同的
throw Exception | 返回false或null | |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
注意:不要把null
添加到队列中,否则poll()
方法返回null
时,很难确定是取到了null
元素还是队列为空
LinkedList
即实现了List
接口,又实现了Queue
接口,但是,在使用的时候,如果我们把它当作List,就获取List的引用,如果我们把它当作Queue,就获取Queue的引用
// 这是一个List: List<String> list = new LinkedList<>(); // 这是一个Queue: Queue<String> queue = new LinkedList<>();
Deque
允许两头都进,两头都出,这种队列叫双端队列(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() |
Deque
接口实际上扩展自Queue
:
public interface Deque<E> extends Queue<E> { ... }
Deque
是一个接口,它的实现类有ArrayDeque
和LinkedList
我们发现LinkedList
真是一个全能选手,它即是List
,又是Queue
,还是Deque
。但是我们在使用的时候,总是用特定的接口来引用它,这是因为持有接口说明代码的抽象层次更高,而且接口本身定义的方法代表了特定的用途。
可见面向抽象编程的一个原则就是:尽量持有接口,而不是具体的实现类。
PriorityQueue
PriorityQueue
和Queue
的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue
调用remove()
或poll()
方法,返回的总是优先级最高的元素。
先看看PriorityQueue
的行为:
public class Main { public static void main(String[] args) { Queue<String> q = new PriorityQueue<>(); // 添加3个元素到队列: q.offer("apple"); q.offer("pear"); q.offer("banana"); System.out.println(q.poll()); // apple System.out.println(q.poll()); // banana System.out.println(q.poll()); // pear System.out.println(q.poll()); // null,因为队列为空 } }
我们放入的顺序是"apple"
、"pear"
、"banana"
,但是取出的顺序却是"apple"
、"banana"
、"pear"
。
因为String实现了Comparable
接口,String默认以字母排序。因此,放入PriorityQueue
的元素,必须实现Comparable
接口,PriorityQueue
会根据元素的排序顺序决定出队的优先级。
如果我们要放入的元素并没有实现Comparable
接口怎么办?PriorityQueue
允许我们提供一个Comparator
对象来判断两个元素的顺序。我们以银行排队业务为例,实现一个PriorityQueue
:
public class Main { public static void main(String[] args) { Queue<User> q = new PriorityQueue<>(new UserComparator()); // 添加3个元素到队列: q.offer(new User("Bob", "A1")); q.offer(new User("Alice", "A2")); q.offer(new User("Boss", "V1")); System.out.println(q.poll()); // Boss/V1 System.out.println(q.poll()); // Bob/A1 System.out.println(q.poll()); // Alice/A2 System.out.println(q.poll()); // null,因为队列为空 } } class UserComparator implements Comparator<User> { public int compare(User u1, User u2) { if (u1.number.charAt(0) == u2.number.charAt(0)) { // 如果两人的号都是A开头或者都是V开头,比较号的大小: return u1.number.compareTo(u2.number); } if (u1.number.charAt(0) == 'V') { // u1的号码是V开头,优先级高: return -1; } else { return 1; } } } class User { public final String name; public final String number; public User(String name, String number) { this.name = name; this.number = number; } public String toString() { return name + "/" + number; } }
实现PriorityQueue
的关键在于提供的UserComparator
对象,它负责比较两个元素的大小(较小的在前)。UserComparator
总是把V
开头的号码优先返回,只有在开头相同的时候,才比较号码大小。
当然,上面的UserComparator
的比较逻辑是有问题的,它会把A10
排在A2
的前面,在这只举个示例。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署