目录
前言-与正文无关
生活远不止眼前的苦劳与奔波,它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中,我们往往容易陷入工作的漩涡,忘记了停下脚步,感受周围的世界。让我们一起提醒自己,要适时放慢脚步,欣赏生活中的每一道风景,享受与家人朋友的温馨时光,发现那些平凡日子里隐藏的幸福时刻。因为,这些点点滴滴汇聚起来的,才是构成我们丰富多彩生活的本质。希望每个人都能在繁忙的生活中找到自己的快乐之源,不仅仅为了生存而工作,更为了更好的生活而生活。
送你张美图!希望你开心!
简介
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下,值得一提的是支持多线程的集合类位于java.util.concurrent包下。
一、Queue接口
队列:是计算机中的一种数据结构,保存在其中的数据具有“先进先出(FIFO,First In First Out)”的特性,新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。
在Java中,队列分为2种形式,一种是单向队列(AbstractQueue),一种是双端队列(Deque)
单向队列和双端队列数据结构
AbstractQueue实现一般都是单向队列,只能对头部数据进行先出;Deque
(双端队列)继承自 Queue
,允许在两端插入和删除元素。因此,Deque
支持双向操作,可以用作栈(LIFO)或队列(FIFO)。其中LinkedList就是Deque接口下比较常见的一个实现类了;
通常,都是使用数组来实现队列,假定数组的长度为5,也就是队列的长度为5;
(一)、单向队列:
1、创建一个长度为5的空数组,定义两个属性front、rear,分别代表着头指针和尾指针。
2、向数组中插入数据
3、移除头部元素:元素1、元素2
4、再向数组中插入数据,此时rear指向一个不存在的下标
此时,数组就会出现“假溢出”的现象,尾指针指向了一个不存在的下标,如果要解决这种情况,一般有两种方法:
1、无限扩充数组容量;
2、使用循环队列。
(二)、循环队列
当尾指针指向了一个不存在的下标时,即超过数组大小时,此时我们判断数组头部是否有空余的空间,如果有就把尾指针指向头部空余的空间,如下图:
阻塞队列
BlockingQueue
继承自 Queue
,是一个支持阻塞操作的队列接口,通常用于线程间通信。它提供了在队列满时阻塞插入操作、在队列空时阻塞移除操作的方法。
BlockingQueue
的常见实现类
ArrayBlockingQueue
: 有界阻塞队列,基于数组。LinkedBlockingQueue
: 可选有界阻塞队列,基于链表。PriorityBlockingQueue
: 支持优先级排序的无界阻塞队列。DelayQueue
: 用于延迟获取元素的无界阻塞队列。SynchronousQueue
: 不存储元素的阻塞队列,每个插入操作必须等待一个删除操作。
方法介绍
Queue 中提供了两套增加、删除元素的 API,当插入或删除元素失败时,会有两种不同的失败处理策略。
方法及失败策略 | 插入方法 | 删除方法 | 查找方法 |
---|---|---|---|
抛出异常 | add() | remove() | element() |
返回false或null | offer() | poll() | peek() |
Queue方法详细介绍:
boolean add(E e):将元素添加到队列的尾部
boolean offer(E e):将元素添加到队列的尾部,功能和add()方法一致
E remove():移除队列头部的元素并将该元素返回
E poll():移除队列头部的元素并将该元素返回,功能和remove()方法一致
E element():获取队列头部的元素,并不会删除该元素
E peek():获取队列头部的元素,并不会删除该元素,功能和element()方法一致
二、AbstractQueue 抽象类
AbstractQueue 类中提供了各个 API 的基本实现,如操作失败时抛出异常还是返回默认值,AbstractQueue 核心就是单向队列,按照规则排序后进行排序,出头部数据。
PriorityQueue
PriorityQueue 是个优先级的无界优先级队列,底层采用数组实现, PriorityQueue 不允许存储 NULL 元素。
优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。
基础用法示例
观察打印结果, 入列:12534, 出列是12345, 也是说出列时做了相关判断,将最小的值返回。默认情况下PriorityQueue使用自然排序法,最小元素先出列。
public class App {
public static void main(String[] args) {
PriorityQueue<String> q = new PriorityQueue<String>();
//入列
q.offer("1");
q.offer("2");
q.offer("5");
q.offer("3");
q.offer("4");
//出列
System.out.println(q.poll()); //1
System.out.println(q.poll()); //2
System.out.println(q.poll()); //3
System.out.println(q.poll()); //4
System.out.println(q.poll()); //5
}
}
自定义示例:
PriorityQueue 应用场景:
元素本身具有优先级,需要按照优先级处理元素。例如游戏中的VIP玩家与普通玩家,VIP 等级越高的玩家越先安排进入服务器玩耍,减少玩家流失。
public static void main(String[] args) {
Student vip1 = new Student("张三", 1);
Student vip3 = new Student("洪七", 2);
Student vip4 = new Student("老八", 4);
Student vip2 = new Student("李四", 1);
Student normal1 = new Student("王五", 0);
Student normal2 = new Student("赵六", 0);
// 根据玩家的 VIP 等级进行降序排序
PriorityQueue<Student> queue = new PriorityQueue<>((o1, o2) -> o2.getScore().compareTo(o1.getScore()));
queue.add(vip1);queue.add(vip4);queue.add(vip3);
queue.add(normal1);queue.add(normal2);queue.add(vip2);
while (!queue.isEmpty()) {
Student s1 = queue.poll();
System.out.println(s1.getName() + "进入游戏; " + "VIP等级: " + s1.getScore());
}
}
public static class Student implements Comparable<Student> {
private String name;
private Integer score;
public Student(String name, Integer score) {
this.name = name;
this.score = score;
}
@Override
public int compareTo(Student o) {
return this.score.compareTo(o.getScore());
}
}
执行上面的代码可以得到下面这种有趣的结果,可以看到氪金
使人带来快乐。
VIP 等级越高(优先级越高)就越优先安排进入游戏(优先处理),类似这种有优先级的场景还有非常多,各位可以发挥自己的想象力。
PriorityQueue 总结:
-
PriorityQueue 是基于优先级堆实现的优先级队列,而堆是用数组维护的
-
PriorityQueue 适用于元素按优先级处理的业务场景,例如用户在请求人工客服需要排队时,根据用户的VIP等级进行
插队
处理,等级越高,越先安排客服。
三、阻塞队列BlockingQueue
BlockingQueue
的常见实现类
ArrayBlockingQueue
: 有界阻塞队列,基于数组。LinkedBlockingQueue
: 可选有界阻塞队列,基于链表。PriorityBlockingQueue
: 支持优先级排序的无界阻塞队列。DelayQueue
: 用于延迟获取元素的无界阻塞队列。SynchronousQueue
: 不存储元素的阻塞队列,每个插入操作必须等待一个删除操作。
ArrayBlockingQueue
描述
- 基于数组的有界阻塞队列,队列的容量在创建时指定,不能改变。
使用场景
- 生产者-消费者模型:在生产者和消费者之间共享一个有限的缓冲区。例如,一个日志系统,其中日志记录器(生产者)将日志消息放入队列,日志处理器(消费者)从队列中读取和处理日志消息。
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// Producer
new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// Consumer
new Thread(() -> {
try {
Integer item = queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
LinkedBlockingQueue
LinkedBlockingQueue
是 Java 并发包 (java.util.concurrent
) 中的一个阻塞队列实现。它基于链表结构,提供了线程安全的队列操作,适用于多线程场景下的生产者-消费者模型,用于任务队列和线程池中的任务调度。例如,Java 的 Executor
框架使用 LinkedBlockingQueue
作为默认的任务队列。以下是 LinkedBlockingQueue
的一些主要特点:
1. 线程安全
LinkedBlockingQueue
内部通过锁(Locks)机制保证了队列的线程安全,确保在多线程环境下数据的一致性和完整性。
2. 阻塞操作
提供了阻塞的插入和移除方法:
put(E e)
方法在队列满时阻塞,直到队列不满。take()
方法在队列为空时阻塞,直到队列中有元素可取。
这些阻塞操作使得 LinkedBlockingQueue
非常适用于生产者-消费者场景。
3. 可选的容量限制
LinkedBlockingQueue
可以是一个可选容量(有界)的队列,也可以是无界的(如果不指定容量,默认为 Integer.MAX_VALUE
)。有界队列可以防止资源耗尽的情况,比如内存溢出。
4. FIFO(先进先出)
LinkedBlockingQueue
是一个基于链表结构的队列,遵循先进先出原则。队列的头部是在队列中时间最长的元素,队列的尾部是在队列中时间最短的元素。
5. 高性能
由于其基于链表的结构,LinkedBlockingQueue
在动态数据操作上表现良好,尤其是在并发环境下插入和删除元素时,性能优于基于数组的阻塞队列(如 ArrayBlockingQueue
)。
6. 公平性选择
构造函数允许选择是否公平性地访问队列。默认情况下,等待时间最长的线程会优先处理,但这可能会影响性能。
PriorityBlockingQueue
描述
- 支持优先级排序的无界阻塞队列,元素根据自然顺序或提供的比较器进行排序。
使用场景
- 优先级任务调度:用于需要按优先级处理任务的场景。例如,调度高优先级任务在低优先级任务之前执行。
BlockingQueue<DelayedTask> queue = new PriorityBlockingQueue<>();
// 添加任务
queue.put(new DelayedTask(1, "Low priority task"));
queue.put(new DelayedTask(10, "High priority task"));
while (!queue.isEmpty()) {
DelayedTask task = queue.take();
task.execute();
}
DelayQueue
描述
- 用于延迟获取元素的无界阻塞队列,只有在延迟期满时才能从队列中取出元素。
使用场景
- 延时任务调度:用于在指定延迟后执行任务。例如,延迟发送通知、定时缓存失效等。
BlockingQueue<DelayedTask> queue = new DelayQueue<>();
// 延迟5秒钟
queue.put(new DelayedTask(5000, "Task 1"));
while (true) {
DelayedTask task = queue.take();
task.execute();
}
SynchronousQueue
描述
- 一个不存储元素的阻塞队列,每个插入操作必须等待一个相应的删除操作,反之亦然。
使用场景
- 任务移交:用于在生产者和消费者之间直接移交任务。例如,一个工作线程将任务直接传递给另一个工作线程,不需要中间的缓冲。
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
ExecutorService executor = Executors.newFixedThreadPool(2);
// Producer thread
executor.submit(() -> {
try {
queue.put(() -> System.out.println("Task executed"));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// Consumer thread
executor.submit(() -> {
try {
Runnable task = queue.take();
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
四、Deque 相关接口
Deque
接口的实现非常好理解:从单向队列演变为双向队列,内部额外提供双向队列的操作方法:
LinkedList
LinkedList 在之前的Java基础 集合(二)系列,已经详细解释了,它实现了 Deque
接口,提供了针对头结点和尾结点的操作,并且每个结点都有前驱和后继指针,底层是双向链表。
ArrayDeque
使用数组实现的双端队列,它是无界的双端队列,默认最小的容量是8
(JDK 1.8)。JDK 11 默认容量已经是 16
了,每次2倍扩容。
ArrayDeque
在日常使用得不多,值得注意的是它与 LinkedList
的对比:LinkedList
采用链表实现双端队列,而 ArrayDeque
使用数组实现双端队列。
ArrayDeque 作为栈时比 Stack 性能好,作为队列时比 LinkedList 性能好
由于双端队列只能在头部和尾部操作元素,所以删除元素和插入元素的时间复杂度大部分都稳定在 O(1)
,除非在扩容时会涉及到元素的批量复制操作。但是在大多数情况下,使用它时应该指定一个大概的数组长度,避免频繁的扩容。
------------------------------------------与正文内容无关------------------------------------
如果觉的文章写对各位读者老爷们有帮助的话,麻烦点赞加关注呗!作者在这拜谢了!
混口饭吃了!如果你需要Java 、Python毕设、商务合作、技术交流、就业指导、技术支持度过试用期。请在关注私信我,本人看到一定马上回复!
这是我全部文章所在目录,看看是否有你需要的,如果遇到觉得不对地方请留言,看到后我会查阅进行改正。
关注在文章左上角,作者信息处。