编程自学指南:java程序设计开发,Java Queue 队列详解
一、课程信息
学习目标
- 理解队列的基本概念和特点。
- 掌握 Java 中 Queue 接口及其常用实现类(如 LinkedList、ArrayDeque)的使用方法。
- 学会使用队列解决实际问题,如任务调度、广度优先搜索等。
- 了解队列的应用场景和重要性。
课程重点
- 队列的先进先出(FIFO)特性。
- Queue 接口的常用方法。
- 不同队列实现类的特点和适用场景。
课程难点
- 理解队列在实际问题中的应用思路。
- 区分不同队列实现类的性能差异。
二、课程导入
生活中的队列场景
在生活中,我们经常会遇到队列的场景。比如在银行排队办理业务,先到的人先办理,后到的人需要在后面排队等待;在食堂打饭时,也是按照排队的顺序依次打饭。这些场景都体现了队列的先进先出(FIFO)特性。
编程中的队列需求
在 Java 编程中,队列也有很多应用场景。例如,在多线程编程中,线程池会使用队列来管理待执行的任务;在网络编程中,消息队列可以用来实现异步通信。使用队列可以有效地管理数据的顺序,确保任务按照一定的顺序执行。
示例代码引入
import java.util.LinkedList;
import java.util.Queue;
public class QueueIntroduction {
public static void main(String[] args) {
// 创建一个队列
Queue<String> queue = new LinkedList<>();
// 向队列中添加元素
queue.add("Alice");
queue.add("Bob");
queue.add("Charlie");
// 从队列中取出元素
String firstPerson = queue.poll();
System.out.println("First person in the queue: " + firstPerson);
}
}
三、队列概述
定义
队列是一种特殊的线性数据结构,遵循先进先出(First In First Out,FIFO)的原则。队列有两个主要操作:入队(enqueue)和出队(dequeue)。入队操作将元素添加到队列的尾部,出队操作从队列的头部移除元素。
特点
- 先进先出(FIFO):最先进入队列的元素最先被取出。
- 有序性:队列中的元素按照入队的顺序排列。
- 双端操作:支持在队列的头部和尾部进行操作。
Java 中的 Queue 接口
Java 中的 Queue
接口是队列的抽象表示,它继承自 Collection
接口。Queue
接口定义了队列的基本操作方法,如 add()
、offer()
、remove()
、poll()
、element()
和 peek()
等。
常用实现类
- LinkedList:基于链表实现的队列,支持高效的插入和删除操作。
- ArrayDeque:基于数组实现的双端队列,支持高效的随机访问和双端操作。
- PriorityQueue:基于优先级堆实现的优先队列,元素按照优先级进行排序。
四、Queue 接口常用方法
入队方法
add(E e)
:将指定的元素插入到队列的尾部。如果队列已满,则抛出IllegalStateException
异常。offer(E e)
:将指定的元素插入到队列的尾部。如果队列已满,则返回false
,否则返回true
。
import java.util.LinkedList;
import java.util.Queue;
public class QueueEnqueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// 使用 add 方法入队
queue.add("Apple");
// 使用 offer 方法入队
boolean result = queue.offer("Banana");
System.out.println("Offer result: " + result);
}
}
出队方法
remove()
:移除并返回队列的头部元素。如果队列为空,则抛出NoSuchElementException
异常。poll()
:移除并返回队列的头部元素。如果队列为空,则返回null
。
import java.util.LinkedList;
import java.util.Queue;
public class QueueDequeueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Cat");
queue.add("Dog");
// 使用 remove 方法出队
String removedElement = queue.remove();
System.out.println("Removed element: " + removedElement);
// 使用 poll 方法出队
String polledElement = queue.poll();
System.out.println("Polled element: " + polledElement);
// 尝试从空队列中出队
String emptyPoll = queue.poll();
System.out.println("Empty poll result: " + emptyPoll);
}
}
查看头部元素方法
element()
:返回队列的头部元素,但不移除。如果队列为空,则抛出NoSuchElementException
异常。peek()
:返回队列的头部元素,但不移除。如果队列为空,则返回null
。
import java.util.LinkedList;
import java.util.Queue;
public class QueuePeekExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Elephant");
// 使用 element 方法查看头部元素
String element = queue.element();
System.out.println("Element: " + element);
// 使用 peek 方法查看头部元素
String peek = queue.peek();
System.out.println("Peek: " + peek);
// 清空队列后尝试查看头部元素
queue.clear();
String emptyPeek = queue.peek();
System.out.println("Empty peek result: " + emptyPeek);
}
}
方法对比总结
操作类型 | 抛出异常 | 返回特殊值 |
---|---|---|
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
查看 | element() | peek() |
五、LinkedList 作为队列的使用
内部实现原理
LinkedList
是一个双向链表,它实现了 Queue
接口。在使用 LinkedList
作为队列时,入队操作相当于在链表的尾部添加元素,出队操作相当于在链表的头部移除元素。
示例代码
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListQueueExample {
public static void main(String[] args) {
// 创建一个使用 LinkedList 实现的队列
Queue<String> queue = new LinkedList<>();
// 入队操作
queue.offer("Fish");
queue.offer("Giraffe");
// 遍历队列
for (String element : queue) {
System.out.println(element);
}
// 出队操作
String firstElement = queue.poll();
System.out.println("Polled element: " + firstElement);
}
}
特点和适用场景
- 特点:插入和删除操作效率高,支持双端操作。
- 适用场景:适用于需要频繁进行插入和删除操作的场景,如任务调度、消息队列等。
六、ArrayDeque 作为队列的使用
内部实现原理
ArrayDeque
是基于数组实现的双端队列,它使用循环数组来存储元素。通过维护两个指针(头指针和尾指针)来实现队列的入队和出队操作。
示例代码
import java.util.ArrayDeque;
import java.util.Queue;
public class ArrayDequeQueueExample {
public static void main(String[] args) {
// 创建一个使用 ArrayDeque 实现的队列
Queue<String> queue = new ArrayDeque<>();
// 入队操作
queue.offer("Horse");
queue.offer("Iguana");
// 查看队列大小
int size = queue.size();
System.out.println("Queue size: " + size);
// 出队操作
String firstElement = queue.poll();
System.out.println("Polled element: " + firstElement);
}
}
特点和适用场景
- 特点:随机访问效率高,支持双端操作,性能优于
LinkedList
。 - 适用场景:适用于需要频繁进行随机访问和双端操作的场景,如栈和队列的混合使用。
七、PriorityQueue 优先队列
内部实现原理
PriorityQueue
是基于优先级堆实现的优先队列,它使用堆数据结构来维护元素的优先级。堆是一种完全二叉树,每个节点的值都大于或等于其子节点的值(最大堆)或小于或等于其子节点的值(最小堆)。
示例代码
import java.util.PriorityQueue;
import java.util.Queue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个优先队列
Queue<Integer> priorityQueue = new PriorityQueue<>();
// 入队操作
priorityQueue.offer(3);
priorityQueue.offer(1);
priorityQueue.offer(2);
// 出队操作,元素按优先级顺序出队
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll());
}
}
}
自定义优先级
可以通过传入自定义的 Comparator
来改变元素的优先级顺序。
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class CustomPriorityQueueExample {
public static void main(String[] args) {
// 自定义比较器,按年龄降序排序
Comparator<Person> ageComparator = Comparator.comparingInt(p -> -p.age);
// 创建一个使用自定义比较器的优先队列
Queue<Person> personQueue = new PriorityQueue<>(ageComparator);
// 入队操作
personQueue.offer(new Person("Jack", 25));
personQueue.offer(new Person("Jill", 20));
personQueue.offer(new Person("John", 30));
// 出队操作,元素按年龄降序出队
while (!personQueue.isEmpty()) {
System.out.println(personQueue.poll());
}
}
}
特点和适用场景
- 特点:元素按照优先级顺序出队,插入和删除操作的时间复杂度为 O (log n)。
- 适用场景:适用于需要根据元素的优先级进行排序和处理的场景,如任务调度、图算法等。
八、队列的实际应用案例
案例 1:任务调度
假设有一个任务队列,每个任务有不同的优先级。使用优先队列来管理这些任务,确保高优先级的任务先执行。
import java.util.PriorityQueue;
import java.util.Queue;
class Task implements Comparable<Task> {
int priority;
String name;
public Task(int priority, String name) {
this.priority = priority;
this.name = name;
}
@Override
public int compareTo(Task other) {
return Integer.compare(other.priority, this.priority);
}
@Override
public String toString() {
return "Task{name='" + name + "', priority=" + priority + "}";
}
}
public class TaskScheduler {
public static void main(String[] args) {
// 创建一个优先队列来管理任务
Queue<Task> taskQueue = new PriorityQueue<>();
// 添加任务到队列
taskQueue.offer(new Task(3, "Task 3"));
taskQueue.offer(new Task(1, "Task 1"));
taskQueue.offer(new Task(2, "Task 2"));
// 执行任务
while (!taskQueue.isEmpty()) {
Task task = taskQueue.poll();
System.out.println("Executing task: " + task);
}
}
}
案例 2:广度优先搜索(BFS)
广度优先搜索是一种用于遍历或搜索树或图的算法,它使用队列来实现。
import java.util.*;
class Graph {
private int V; // 顶点数
private LinkedList<Integer>[] adj; // 邻接表
public Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
// 添加边
public void addEdge(int v, int w) {
adj[v].add(w);
}
// 广度优先搜索
public void BFS(int s) {
boolean[] visited = new boolean[V];
Queue<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
s = queue.poll();
System.out.print(s + " ");
Iterator<Integer> i = adj[s].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
}
public class BFSTraversal {
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("Following is Breadth First Traversal " +
"(starting from vertex 2)");
g.BFS(2);
}
}
九、课堂练习
练习 1
使用队列实现一个简单的消息队列,支持消息的入队和出队操作,并打印出队的消息。
import java.util.LinkedList;
import java.util.Queue;
public class MessageQueueExercise {
public static void main(String[] args) {
// 请完成代码
}
}
练习 2
使用优先队列对一组学生对象按照成绩从高到低进行排序,并输出排序后的结果。
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{name='" + name + "', score=" + score + "}";
}
}
public class StudentPriorityQueueExercise {
public static void main(String[] args) {
// 请完成代码
}
}
十、课程总结
重点回顾
- 队列的基本概念和特点,特别是先进先出(FIFO)特性。
- Queue 接口的常用方法,包括入队、出队和查看头部元素的方法。
- 不同队列实现类(LinkedList、ArrayDeque、PriorityQueue)的特点和适用场景。
- 队列在实际问题中的应用,如任务调度和广度优先搜索。
注意事项
- 在使用队列时,要注意区分不同方法在队列为空或已满时的行为,避免抛出异常。
- 对于优先队列,要确保元素实现了
Comparable
接口或传入了自定义的Comparator
。
十一、课后作业
作业 1
实现一个循环队列,支持入队、出队和查看队列大小的操作。
作业 2
使用队列实现一个简单的缓存系统,当缓存满时,按照先进先出的原则移除最早加入的元素。
作业 3
在广度优先搜索的基础上,实现一个最短路径算法,计算从起点到终点的最短路径。