Java基础 集合(三)Queue详解

目录

 简介

 一、Queue接口

单向队列和双端队列数据结构

  (一)、单向队列:

(二)、循环队列

阻塞队列

BlockingQueue 的常见实现类

方法介绍

二、AbstractQueue 抽象类

PriorityQueue

基础用法示例

三、阻塞队列BlockingQueue

BlockingQueue 的常见实现类

ArrayBlockingQueue

描述

使用场景

LinkedBlockingQueue

1. 线程安全

2. 阻塞操作

3. 可选的容量限制

4. FIFO(先进先出)

5. 高性能

6. 公平性选择

PriorityBlockingQueue

描述

使用场景

DelayQueue

描述

使用场景

SynchronousQueue

描述

使用场景

四、Deque 相关接口

LinkedList

ArrayDeque


前言-与正文无关

        生活远不止眼前的苦劳与奔波,它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中,我们往往容易陷入工作的漩涡,忘记了停下脚步,感受周围的世界。让我们一起提醒自己,要适时放慢脚步,欣赏生活中的每一道风景,享受与家人朋友的温馨时光,发现那些平凡日子里隐藏的幸福时刻。因为,这些点点滴滴汇聚起来的,才是构成我们丰富多彩生活的本质。希望每个人都能在繁忙的生活中找到自己的快乐之源,不仅仅为了生存而工作,更为了更好的生活而生活。

        送你张美图!希望你开心!

 简介

       集合可以看作是一种容器,用来存储对象信息。所有集合类都位于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或nulloffer()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 接口,提供了针对头结点和尾结点的操作,并且每个结点都有前驱后继指针,底层是双向链表

Java基础 集合(二)List详解-CSDN博客

ArrayDeque

使用数组实现的双端队列,它是无界的双端队列,默认最小的容量是8(JDK 1.8)。JDK 11 默认容量已经是 16了,每次2倍扩容

ArrayDeque 在日常使用得不多,值得注意的是它与 LinkedList 的对比:LinkedList 采用链表实现双端队列,而 ArrayDeque 使用数组实现双端队列。 

ArrayDeque 作为栈时比 Stack 性能好,作为队列时比 LinkedList 性能好

由于双端队列只能在头部和尾部操作元素,所以删除元素和插入元素的时间复杂度大部分都稳定在 O(1) ,除非在扩容时会涉及到元素的批量复制操作。但是在大多数情况下,使用它时应该指定一个大概的数组长度,避免频繁的扩容。

------------------------------------------与正文内容无关------------------------------------
 如果觉的文章写对各位读者老爷们有帮助的话,麻烦点赞加关注呗!作者在这拜谢了!

混口饭吃了!如果你需要Java 、Python毕设、商务合作、技术交流、就业指导、技术支持度过试用期。请在关注私信我,本人看到一定马上回复!

这是我全部文章所在目录,看看是否有你需要的,如果遇到觉得不对地方请留言,看到后我会查阅进行改正。

A乐神-CSDN博客

关注在文章左上角,作者信息处。

  • 29
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中的队列(Queue)是一种数据结构,它按照先进先出(FIFO)的原则进行操作。Java提供了许多不同类型的队列实现,其中最常用的是LinkedList和ArrayDeque。 LinkedList可以用作队列的实现,它提供了添加元素到队尾、获取队头元素、移除队头元素等基本操作。以下是使用LinkedList实现队列的示例代码: ```java import java.util.LinkedList; import java.util.Queue; public class QueueExample { public static void main(String[] args) { Queue<String> queue = new LinkedList<>(); // 添加元素到队尾 queue.offer("Apple"); queue.offer("Banana"); queue.offer("Orange"); // 获取队头元素 String head = queue.peek(); System.out.println("Queue head: " + head); // 移除队头元素 String removedElement = queue.poll(); System.out.println("Removed element: " + removedElement); // 遍历队列中的元素 System.out.println("Elements in the queue:"); for (String element : queue) { System.out.println(element); } } } ``` 另一种常用的队列实现是ArrayDeque,它是一个基于数组的双端队列。ArrayDeque既可以用作栈,也可以用作队列。以下是使用ArrayDeque实现队列的示例代码: ```java import java.util.ArrayDeque; import java.util.Queue; public class QueueExample { public static void main(String[] args) { Queue<String> queue = new ArrayDeque<>(); // 添加元素到队尾 queue.offer("Apple"); queue.offer("Banana"); queue.offer("Orange"); // 获取队头元素 String head = queue.peek(); System.out.println("Queue head: " + head); // 移除队头元素 String removedElement = queue.poll(); System.out.println("Removed element: " + removedElement); // 遍历队列中的元素 System.out.println("Elements in the queue:"); for (String element : queue) { System.out.println(element); } } } ``` 这两种队列实现都可以根据具体的需求选择使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A乐神

恭喜发财啊,老板,嘻嘻!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值