传送门
SpringMVC的源码解析(精品)
Spring6的源码解析(精品)
SpringBoot3框架(精品)
MyBatis框架(精品)
MyBatis-Plus
SpringDataJPA
SpringCloudNetflix
SpringCloudAlibaba(精品)
Shiro
SpringSecurity
java的LOG日志框架
Activiti(敬请期待)
JDK8新特性
JDK9新特性
JDK10新特性
JDK11新特性
JDK12新特性
JDK13新特性
JDK14新特性
JDK15新特性
JDK16新特性
JDK17新特性
JDK18新特性
JDK19新特性
JDK20新特性
JDK21新特性
其他技术文章传送门入口
一、api
https://www.liaoxuefeng.com/wiki/1252599548343744/1265121791832960
在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()方法,当添加失败时(可能超过了队列的容量),它会抛出异常:
Queue<String> q = ...
try {
q.add("Apple");
System.out.println("添加成功");
} catch(IllegalStateException e) {
System.out.println("添加失败");
}
如果我们调用offer()方法来添加元素,当添加失败时,它不会抛异常,而是返回false:
Queue<String> q = ...
if (q.offer("Apple")) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
因此,两套方法可以根据需要来选择使用。
注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。
二、LinkList
// 这是一个List:
List<String> list = new LinkedList<>();
// 这是一个Queue:
Queue<String> queue = new LinkedList<>();
三、PriorityQueue
Queue是严格先进先出,而PriorityQueue要传入Comparator,元素有优先级,优先级高的先出。
我们知道,Queue是一个先进先出(FIFO)的队列。
在银行柜台办业务时,我们假设只有一个柜台在办理业务,但是办理业务的人很多,怎么办?
可以每个人先取一个号,例如:A1、A2、A3……然后,按照号码顺序依次办理,实际上这就是一个Queue。
如果这时来了一个VIP客户,他的号码是V1,虽然当前排队的是A10、A11、A12……但是柜台下一个呼叫的客户号码却是V1。
这个时候,我们发现,要实现“VIP插队”的业务,用Queue就不行了,因为Queue会严格按FIFO的原则取出队首元素。我们需要的是优先队列:PriorityQueue。
PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
要使用PriorityQueue,我们就必须给每个元素定义“优先级”。我们以实际代码为例,先看看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”,这是因为从字符串的排序看,"apple"排在最前面,"pear"排在最后面。
因此,放入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) == &apos;V&apos;) {
// 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的前面,请尝试修复该错误。
class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
return u1.number.charAt(0) == u2.number.charAt(0) ?
Integer.compare(Integer.parseInt(u1.number.substring(1, u1.number.length())),
Integer.parseInt(u2.number.substring(1, u2.number.length()))) :
u1.number.charAt(0) == &apos;V&apos; ? -1:1;
}
}