🚀 队列 (Queue)
🔎 特点
- FIFO: 先进先出(
FIFO
)数据结构,队列中的元素按照它们被添加的顺序进行检索。 - 尾进头出: 从一端(
队尾
)进入,并从另一端(队头
)出来 - 有界与无界: 队列可以是有界的(限定大小)或无界的(大小由内存限制)。
- 支持并发操作: 部分队列支持多线程并发操作,如添加、删除等。
- 阻塞与非阻塞: 队列操作可以是阻塞的或非阻塞的。
🔍 Java中常用的队列及其类型
LinkedList
: 实现了Deque
接口,可以作为双端队列使用。PriorityQueue
: 一个基于优先级的队列。ArrayDeque
: 双端队列,更高效地使用内存。ConcurrentLinkedQueue
: 一个非阻塞并发队列。LinkedBlockingQueue
: 一个基于链表的阻塞队列。ArrayBlockingQueue
: 一个基于数组的有界阻塞队列。SynchronousQueue
: 一个不存储元素的阻塞队列。DelayQueue
: 使用优先级队列实现的阻塞队列。
📌 有界队列 (Bounded Queue
)
-
🎯 特点:
- 📎 有界队列是一个固定大小的队列。
- 📎 当队列满时,任何尝试入队的新元素都会被阻塞或抛出异常,直到有空间为止。
- 📎 当队列为空时,尝试出队的操作也可能被阻塞或返回一个特殊值。
-
🔍 结构:
通常实现为一个数组或循环数组。 -
🛠️ 使用场景:
- 📎 当需要限制队列大小以控制内存使用时。
- 📎 在生产者-消费者模型中,当生产者和消费者的处理速度有差异时使用。
-
📌 使用方式:
import java.util.concurrent.ArrayBlockingQueue; ArrayBlockingQueue<Integer> boundedQueue = new ArrayBlockingQueue<>(10); // 创建一个容量为10的有界队列 boundedQueue.put(1); // 添加元素; 当队列满时,此方法会阻塞 boundedQueue.take(); // 获取并移除队列的头部; 当队列为空时,此方法会阻塞
-
⚠️ 注意: 超出容量的操作可能导致阻塞或异常,这取决于具体的实现方式。
📌 无界队列 (Unbounded Queue
)
-
🎯 特点:
- 📎 无界队列没有固定的大小限制。
- 📎 在理论上,它可以无限增长,受到的限制只是可用的内存。
-
🔍 结构:
通常实现为链表。 -
🛠️ 使用场景:
- 📎 在任务的数量无法预先确定或者有大量的短暂任务需要处理时。
- 📎 用于线程池,例如
ThreadPoolExecutor
中的任务队列。
-
📌 使用方式:
import java.util.concurrent.LinkedBlockingQueue; LinkedBlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>(); // 创建一个无界队列 unboundedQueue.put(1); // 添加元素 unboundedQueue.take(); // 获取并移除队列的头部; 当队列为空时,此方法会阻塞
-
⚠️ 注意: 虽然无界队列在理论上是无限的,但实际上它受到物理内存的限制。
📌 优先队列 (Priority Queue
)
-
🎯 特点:
- 📎 元素出队的顺序是根据其优先级,而不是它们进入队列的顺序。
- 📎 可以有多种实现方式,但最常见的是使用堆。
-
🔍 结构:
堆是一种常见的优先队列的实现。 -
🛠️ 使用场景:
- 📎 用于任务调度,其中任务有不同的优先级。
- 📎 Dijkstra’s算法,其中需要按照节点的最短距离选择下一个节点。
-
📌 使用方式:
import java.util.PriorityQueue; PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.add(3); priorityQueue.add(1); priorityQueue.add(2); System.out.println(priorityQueue.poll()); // 输出: 1
-
⚠️ 注意: 优先队列不保证同等优先级元素的顺序。
在Java的PriorityQueue
中,元素的优先级是通过以下方式定义的:
-
自然排序: 如果队列中的元素类实现了
Comparable
接口,那么元素之间的优先级是按照compareTo
方法定义的自然顺序来确定的。 -
使用比较器 (Comparator): 如果队列的元素类没有实现
Comparable
接口或者你想提供一个与自然顺序不同的排序方式,你可以为PriorityQueue
提供一个Comparator
对象。这个Comparator
对象的compare
方法定义了元素之间的优先级。 -
自然排序 (使用
Comparable
):
class Task implements Comparable<Task> {
private int priority;
public Task(int priority) {
this.priority = priority;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
}
PriorityQueue<Task> queue = new PriorityQueue<>();
- 使用
Comparator
:
class Task {
private int priority;
public Task(int priority) {
this.priority = priority;
}
public int getPriority() {
return priority;
}
}
Comparator<Task> taskComparator = new Comparator<Task>() {
@Override
public int compare(Task t1, Task t2) {
return Integer.compare(t1.getPriority(), t2.getPriority());
}
};
PriorityQueue<Task> queue = new PriorityQueue<>(taskComparator);
在这两种情况下,当从队列中拉取任务时,优先级较高的任务(即优先级值较小的任务,因为上述示例中的比较方法使用的是自然的整数顺序)将首先被处理。
📌 延迟队列 (Delayed Queue
)
-
🎯 特点:
- 📎 只有当元素的预定时间到达时,它才能从队列中被取出。
- 📎 元素必须定义它们的延迟策略。
-
🔍 结构:
通常实现为一个优先队列,其中优先级是基于元素的延迟时间。 -
🛠️ 使用场景:
- 📎 任务调度,当任务需要在特定的未来时间执行时。
- 📎 缓存系统,当需要在指定时间后自动删除过期的缓存项。
-
📌 使用方式:
import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; class DelayedElement implements Delayed { private long delayTime; private long expireTime; public DelayedElement(long delayTime) { this.delayTime = delayTime; this.expireTime = System.currentTimeMillis() + delayTime; } @Override public long getDelay(TimeUnit unit) { return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); } } DelayQueue<DelayedElement> delayQueue = new DelayQueue<>(); delayQueue.put(new DelayedElement(5000)); // 5秒后到期的元素 // 获取并移除队列的头部; 如果没有到期的元素,此方法会阻塞 delayQueue.take();
-
⚠️ 注意: 尽管元素被排队,但直到其预定的延迟时间到达之前,它都不能被消费。
🌍 常用场景
- 任务调度: 使用优先级队列来调度任务的执行。
- 数据流处理: 在多线程环境中,生产者-消费者模型常用队列来传递数据。
- 并发控制: 使用阻塞队列来控制并发访问数量。
🔧 工具类的使用
1️⃣ Apache Commons Collections:
Apache Commons Collections 提供了一系列增强的数据结构和算法,对于队列来说,它扩充了 Java SDK 提供的队列功能。
CircularFifoQueue
: 是一个有界的循环FIFO队列,当它满了之后,新元素会覆盖最旧的元素。
CircularFifoQueue<String> queue = new CircularFifoQueue<>(3);
queue.add("A");
queue.add("B");
queue.add("C");
queue.add("D"); // This will remove A from the queue
PredicatedQueue
: 允许你添加一个断言到队列中,只有当元素满足断言时才能被添加。
Predicate<Object> predicate = o -> (o instanceof String);
Queue<String> queue = PredicatedQueue.predicatedQueue(new LinkedList<>(), predicate);
2️⃣ Google Guava:
Google Guava 是另一个功能丰富的 Java 库,它提供了一些队列的增强功能。
EvictingQueue
: 是一个固定大小的队列,当达到其最大大小时,新元素的加入会导致老元素被移除。这与CircularFifoQueue
类似,但提供了更多的功能。
EvictingQueue<String> queue = EvictingQueue.create(3);
queue.add("1");
queue.add("2");
queue.add("3");
queue.add("4"); // This will cause "1" to be evicted
⚠️ 注意: 以上的工具类并不能代替Java SDK中的队列,而是为特定的应用场景提供了额外的功能。在选择时,应当基于实际需求来选择最合适的工具类。