一、Queue是什么
该接口是Java集合框架成员
Queue: 通常(但不一定)队列就是一个先入先出(FIFO)的数据结构,和堆一样(但可以进行转换,比如优先级列队排序,又或者改为栈形式的后进先出数据结构, 不论如何,都可以用remove()或poll()来调节)
Queue接口与List、Set同一级别,都是继承了Collection接口。
LinkedList实现的Deque接口中继承了Queue,所以在LinkedList中就有Queue重写的方法,同时也可以多态的声明Queue队列对象
队列实现通常不允许插入null,但不禁止插入null。
数据可重复,保证有序性(基于数据类型)、不可以存储null。
队列是数据结构中比较重要的一种类型(是一种数据结构),它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
队列是一种比较特殊的线性结构。它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。
换言之,queue不允许有遍历行为。
有特殊情况吗?比如在前端进行插入操作?有,JDK在1.6的时候新增了一个双向队列Deque,用来实现更灵活的队列操作。比如可以在前端插入数据。
队列中最先插入的元素也将最先被删除,对应的最后插入的元素将最后被删除。因此队列又称为“先进先出”(FIFO—first in first out)的线性表,与栈(FILO-first in last out)刚好相反。
java中的Queue接口就实现了队列的功能,Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList虽然是个数组,但是也实现了Queue接口(通过Deque接口间接实现),因此,可以当做Queue来用。
Queue接口的特点:
- 队列的主要特点是在基本的集合方法之外,还提供特殊的插入、获取和检验操作。每个操作都提供两个方法,一种返回异常,一种返回null或者false.
- 队列一般满足先进先出规则(FIFO),除了优先队列(priority queue)和栈(stack),但是栈是FILO(先进后出规则),优先队列自己定义了排序规则。
二、Queue的实现
1、没有实现的阻塞接口的LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口
内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue
PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现。
PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小。 ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
2)实现阻塞接口的:
java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
五个队列所提供的各有不同:
* ArrayBlockingQueue :一个由数组支持的有界队列。
* LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
* PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
* DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
* SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。
下表显示了jdk1.5中的阻塞队列的操作:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
remove、element、offer 、poll、peek 其实是属于Queue接口。
Queue接口常用API
阻塞队列的操作可以根据它们的响应方式分为以下三类:aad、removee和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。
注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的
最后,我们有阻塞操作put和take。put方法在队列满时阻塞,take方法在队列空时阻塞。
图中我们可以看到,最上层是Collection接口
,Queue满足集合类的所有方法,都是非阻塞的。
add(E e):增加元素; remove(Object o):删除元素; clear():清除集合中所有元素; size():集合元素的大小; isEmpty():集合是否没有元素; contains(Object o):集合是否包含元素o。
BlockingQueue接口继承Queue接口,也扩展了一些方法:
put(E e); //阻塞 take(); //阻塞
知道这个原理,可以帮助我们记忆一些特性,比如辨别是否阻塞方法,那么联想 add(E e)既然是Collection接口定义的,那么一般就是非阻塞的,因为同样的实现Collection接口的ArrayList也是非阻塞的。
2.1 阻塞队列
2.1.1 BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞队列实现了阻塞接口 BlockingQueue 。
java.util.concurrent中加入了 BlockingQueue 接口和五个阻塞队列类。
方法介绍
阻塞队列提供了四种处理方法:
- 一直阻塞
当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。
当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
- 超时退出
当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
当阻塞队为空时,队列会阻塞消费者线程一段时间,如果超过一定的时间,消费者线程就会退出。
非阻塞方法remove、element、offer 、poll、peek 其实是属于Queue接口
。 而阻塞方法 put
、 take
是定义在BlockingQueue接口
中
阻塞队列的成员:
下面分别简单介绍一下。
- ArrayBlockingQueue(详情参见: 《ArrayBlockingQueue》)
是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。
构造函数必须指定大小
【注:每一个线程在获取锁的时候可能都会排队等待,如果在等待时间上,先获取锁的线程的请求一定先被满足,那么这个锁就是公平的。反之,这个锁就是不公平的。公平的获取锁,也就是当前等待时间最长的线程先获取锁】
ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:
一个由链表
结构组成的有界队列,此队列按照先进先出的顺序进行排序。
有界队列,如果不指定大小,则此队列的默认长度为Integer.MAX_VALUE
。
LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
- PriorityBlockingQueue:
一个支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则,不能保证同优先级元素的顺序。
但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者(无界的,队列永远不会满,无法触发队列满阻塞),而只会在没有可消费的数据时,阻塞数据的消费者(即可以触发空阻塞)。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。
- DelayQueue:
一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
DelayQueue可以运用在以下应用场景:
1.缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
2.定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。
- SynchronousQueue:
一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
2.1.2 BlockingDeque
BlockingDeque
(阻塞双端队列)在Deque
的基础上实现了双端阻塞等待的功能。和第2节说的类似,BlockingDeque
也提供了双端队列该有的阻塞等待方法:
putFirst(E e):在队首插入元素,如果队列满了,阻塞等待,直到被中断为止。 putLast(E e):在队尾插入元素,如果队列满了,阻塞等待,直到被中断为止。 offerFirst(E e, long timeout, TimeUnit unit):向队首插入元素。如果队列满了,阻塞等待timeout个时长,如果到了超时时间还没有空间,抛弃该元素。 offerLast(E e, long timeout, TimeUnit unit):向队尾插入元素。如果队列满了,阻塞等待timeout个时长,如果到了超时时间还没有空间,抛弃该元素。 takeFirst():获取并移除队首的元素。如果队列为空,阻塞等待,直到被中断为止。 takeLast():获取并移除队尾的元素。如果队列为空,阻塞等待,直到被中断为止。 pollFirst(long timeout, TimeUnit unit):获取并移除队首的元素。如果队列为空,阻塞等待timeout个时长,如果到了超时时间还没有元素,则返回null。 pollLast(long timeout, TimeUnit unit):获取并移除队尾的元素。如果队列为空,阻塞等待timeout个时长,如果到了超时时间还没有元素,则返回null。 removeFirstOccurrence(Object o):从队首开始移除第一个和o相等的元素。 removeLastOccurrence(Object o):从队尾开始移除第一个和o相等的元素。
从图中我们可以知道实现了BlockingDeque的类有:
- LinkedBlockingDeque:
一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半。
2.1.3 TransferQueue
TransferQueue是JDK 1.7对于并发类库新增加的一个接口,它扩展自BlockingQueue,所以自然保持着阻塞队列的所有特性。
有人这样评价它:TransferQueue是是ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues等的超集。
TransferQueue对比与BlockingQueue更强大的一点是,生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立Java内存模型中的happens-before关系的方式)。
我们来看看该接口提供的标准方法:
tryTransfer(E e):若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e并立即返回true;若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作。 transfer(E e):若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。 tryTransfer(E e, long timeout, TimeUnit unit):若当前存在一个正在等待获取的消费者线程,会立即传输给它;否则将插入元素e到队列尾部,并且等待被消费者线程获取消费掉;若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除。 hasWaitingConsumer():判断是否存在消费者线程。 getWaitingConsumerCount():获取所有等待获取元素的消费线程数量。
其实transfer方法在SynchronousQueue的实现中就已存在了,只是没有做为API暴露出来。SynchronousQueue有一个特性:它本身不存在容量,只能进行线程之间的元素传送。
SynchronousQueue在执行offer操作时,如果没有其他线程执行poll,则直接返回false.线程之间元素传送正是通过transfer方法完成的。
TransferQueue相比SynchronousQueue用处更广、更好用,因为你可以决定是使用BlockingQueue的方法(例如put方法)还是确保一次传递完成(即transfer方法)。在队列中已有元素的情况下,调用transfer方法,可以确保队列中被传递元素之前的所有元素都能被处理。
从图中我们可以知道实现了TransferQueue的类有:
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
好了,队列的API先说到这里,下面我会另起一文重点说说阻塞队列的那些个实现类的原理。
2.2 非阻塞队列
内置的不阻塞队列: PriorityQueue
和 ConcurrentLinkedQueue
PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现。
- PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
- ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
入队和出队操作均利用CAS(compare and set)更新,这样允许多个线程并发执行,并且不会因为加锁而阻塞线程,使得并发性能更好。
没有实现阻塞接口的LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口
2.3 到底什么是阻塞队列
区分阻塞队列和非阻塞队列的关键因素是什么?
按照大多数文章的介绍,阻塞队列支持阻塞特性,在队列满或为空时会阻塞,但是我想到队列必须是线程安全的,阻塞队列(前文表格中的那5个阻塞队列)都是利用悲观加锁,互斥做到线程安全的,貌似加锁在某种程度上也等价于阻塞,巧合的是非阻塞队列(比如ConcurrentLinkedQueue)又是利用乐观锁实现线程安全的,乐观锁可以理解成未加锁。
在《JAVA中的阻塞队列和非阻塞队列》一文中,介绍非阻塞队列时有如下:
基于锁的算法会带来一些活跃度失败的风险。如果线程在持有锁的时候因为阻塞I/O、页面错误、或其他原因发生延迟,很可能所有的线程都不能工作了。一个线程的失败或挂起不应该影响其他线程的失败或挂起,这样的算法称为非阻塞算法;如果算法的每一个步骤中都有一些线程能够继续执行,那么这样的算法称为锁自由(lock-free)算法。在线程间使用CAS进行协调,这样的算法如果能构建正确的话,它既是非阻塞的,又是锁自由的。java中提供了基于CAS非阻塞算法实现的队列,比较有代表性的有ConcurrentLinkedQueue和LinkedTransferQueue,它们的性能一般比阻塞队列的好。
那么如果回答什么是阻塞队列,什么是非阻塞队列时,是不是2个要素都要提及?
答案
阻塞队列,是指多线程访问竞争资源时,当竞争资源已被某线程获取时,其它要获取该资源的线程需要阻塞等待!
虽然队列满了,会休眠,出队发现为空,就等待,也是阻塞,但不是阻塞队列的核心概念!
三、案例
1.算法案例
/** * 入口 * 101. 对称二叉树 * 1.创建链表root * 输入: * TreeNode root = [1,2,2,3,4,4,3] * 输出: * result = true * 解释: * 1.递归 * 2.迭代(Queue队列) */ @Test public void suanfa21() { // 创建树 TreeNode root = new TreeNode(1, new TreeNode(2, new TreeNode(3), new TreeNode(4)), new TreeNode(2, new TreeNode(4), new TreeNode(3)));// 调用方法:迭代 boolean result2 = this.isSymmetricIteration(root); System.out.println("results2 = " + result2); } /** * 迭代方案 * * @param root 二叉树 * @return */ public boolean isSymmetricIteration(TreeNode root) { // Queue队列 和 堆的处理方案一样,都是先进先出(FIFO)的数据结构 Queue<TreeNode> treeNodes = new LinkedList<>(); // 存入根节点的 左子树 和 右子树 treeNodes.offer(root.left); treeNodes.offer(root.right); // 如果Queue队列不为空就循环比较 while (!treeNodes.isEmpty()) { // 获取存入的左子树 和 右子树 (因为先进先出,所以可以确认顺序) TreeNode rootLeft = treeNodes.poll(); TreeNode rootRight = treeNodes.poll(); // 两个都等于null, 所以没有子节点了,就继续判断队列中是否还有值,有则比较,无则结束,返回true if (rootLeft == null && rootRight == null) { continue; } // 左子树 或 右子树,如果有一个是null,就说明不相等,直接返回false,因为上面的判断,所以不可能两个都是null if (rootLeft == null || rootRight == null) { return false; } // 左子树 比较 右子树 的值不相等,直接返回false if (rootLeft.val != rootRight.val) { return false; } // 存入 左子树的左子树 和 右子树的右子树,这样当取出比较相等就说明是镜像 treeNodes.offer(rootLeft.left); treeNodes.offer(rootRight.right); // 存入 左子树的右子树 和 右子树的左子树,这样当取出比较相等就说明是镜像 treeNodes.offer(rootLeft.right); treeNodes.offer(rootRight.left); } // 当执行到这里说明树两边是镜像的 return true; }
2.多线程案例
package com.yao; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BlockingQueueTest { /** 定义装苹果的篮子 */ public static class Basket{ // 篮子,能够容纳3个苹果 BlockingQueue<String> basket = new ArrayBlockingQueue<String>(3); // 生产苹果,放入篮子 public void produce() throws InterruptedException{ // put方法放入一个苹果,若basket满了,等到basket有位置 basket.put("An apple"); } // 消费苹果,从篮子中取走 public String consume() throws InterruptedException{ // get方法取出一个苹果,若basket为空,等到basket有苹果为止 String apple = basket.take(); return apple; } public int getAppleNumber(){ return basket.size(); } } // 测试方法 public static void testBasket() { // 建立一个装苹果的篮子 final Basket basket = new Basket(); // 定义苹果生产者 class Producer implements Runnable { public void run() { try { while (true) { // 生产苹果 System.out.println("生产者准备生产苹果:" + System.currentTimeMillis()); basket.produce(); System.out.println("生产者生产苹果完毕:" + System.currentTimeMillis()); System.out.println("生产完后有苹果:"+basket.getAppleNumber()+"个"); // 休眠300ms Thread.sleep(300); } } catch (InterruptedException ex) { } } } // 定义苹果消费者 class Consumer implements Runnable { public void run() { try { while (true) { // 消费苹果 System.out.println("消费者准备消费苹果:" + System.currentTimeMillis()); basket.consume(); System.out.println("消费者消费苹果完毕:" + System.currentTimeMillis()); System.out.println("消费完后有苹果:"+basket.getAppleNumber()+"个"); // 休眠1000ms Thread.sleep(1000); } } catch (InterruptedException ex) { } } } ExecutorService service = Executors.newCachedThreadPool(); Producer producer = new Producer(); Consumer consumer = new Consumer(); service.submit(producer); service.submit(consumer); // 程序运行10s后,所有任务停止 try { Thread.sleep(10000); } catch (InterruptedException e) { } service.shutdownNow(); } public static void main(String[] args) { BlockingQueueTest.testBasket(); } }
3.top K问题
查找指定数据中出现次数最多的前K个数据:
比如:求前K个最大数据,我们创建一个容量为K的底层为小根堆的队列,每次插入数据只需要与根数据比较决定是否插入即可,相反求最小数据就创建一个大跟堆。
public static void BufferedReaderEx(String str) {//指定文件目录 File file = new File(str); //file实例 FileReader reader = null; //读取数据并存储在list集合中 try { reader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(reader); String tmp; ArrayList<String> list = new ArrayList<>(); while ((tmp = bufferedReader.readLine()) != null) { String[] split = tmp.split(","); for (String s:split){ list.add(s); } } HashMap<String,Integer> map = HashMapEx(list); PriorityQueue<Map.Entry<String, Integer>> queue = QueueEx(map,5); while (!queue.isEmpty()){ Map.Entry<String, Integer> remove = queue.remove(); System.out.println("数字:" + remove.getKey() + ",出现:" +remove.getValue() + "次"); } } catch (IOException e) { e.printStackTrace(); } } public static HashMap<String,Integer> HashMapEx(ArrayList<String> list){//将数据存储到HashMap中用来统计一个数组出现的次数 key为数据 value为出现次数 HashMap<String, Integer> map = new HashMap<>(); while (!list.isEmpty()){ String index = list.remove(0); if (map.containsKey(index)) { map.put(index, map.get(index) + 1); } else { map.put(index, 1); } } return map; } public static PriorityQueue<Map.Entry<String, Integer>> QueueEx(HashMap<String,Integer> map,int num){//将Map中的Entry存储到PriorityQueue队列中并提供一个关于Entry.value的比较器并将队列的容量设置为K PriorityQueue<Map.Entry<String, Integer>> queue = new PriorityQueue<>(num, new Comparator<Map.Entry<String, Integer>>() { @Override public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { return (Integer) o1.getValue() - (Integer) o2.getValue(); } }); Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()){//遍历Map集合 如果队列容量小于K直接插入数据否则与queue顶部数据进行Value计较如果大于顶部数据插入到queue中 Map.Entry<String, Integer> next = iterator.next(); if(queue.size()<num){ queue.add(next); }else { if(next.getValue()>queue.peek().getValue()){ queue.remove(); queue.add(next); } } } return queue; }
Queue接口保证数据有序性(基于数据类型)、不可以存储nullPriorityQueue介绍可以自定义比较器用来实现自己想要的底层结构方便我们最值问题和topK问题的求解。
Deque类
Deque是一个双端队列接口,继承Queue接口,Deque的实现类是LinkedList、ArrayDeque和LinkedBlockingDeque,其中LinkedList是最常用的。其中LinkedBlockingDeque是一个基于链表实现的双向阻塞队列。
Deque在Queue的基础上,增加了以下几个方法:
addFirst(E e):在前端插入元素,异常处理和add一样; addLast(E e):在后端插入元素,和add一样的效果; offerFirst(E e):在前端插入元素,异常处理和offer一样; offerLast(E e):在后端插入元素,和offer一样的效果; removeFirst():移除前端的一个元素,异常处理和remove一样; removeLast():移除后端的一个元素,和remove一样的效果; pollFirst():移除前端的一个元素,和poll一样的效果; pollLast():移除后端的一个元素,异常处理和poll一样; getFirst():获取前端的一个元素,和element一样的效果; getLast():获取后端的一个元素,异常处理和element一样; peekFirst():获取前端的一个元素,和peek一样的效果; peekLast():获取后端的一个元素,异常处理和peek一样; removeFirstOccurrence(Object o):从前端开始移除第一个是o的元素; removeLastOccurrence(Object o):从后端开始移除第一个是o的元素; push(E e):和addFirst一样的效果; pop():和removeFirst一样的效果。
可以发现,其实很多方法的效果都是一样的,只不过名字不同。比如Deque为了实现Stack的语义,定义了push
和pop
两个方法。
注意区分:LinkedBlockingQueue和LinkedBlockingDeque,两个都是队列,只不过前者只能一端出一端入,后者则可以两端同时出入,
Deque有三种用途:普通队列(一端进另一端出)、双端队列(两端都可进出)、堆栈。
- 普通队列
Queue queue = new LinkedList() 或 Deque deque = new LinkedList()
- 双端队列
Deque deque = new LinkedList()
Deque deque1 = new LinkedBlockingDeque();
LinkedBlockingDeque deque2 = new LinkedBlockingDeque();
- 堆栈
Deque deque = new LinkedList()
代码演示
1、实体类
package cn.com.easyExcel.point; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @NoArgsConstructor public class PointValue implements Serializable { private static final long serialVersionUID = -3925356810846060856L; // 值 public BigDecimal value; // 某时刻 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) public LocalDateTime time; public static PointValue of(BigDecimal value, LocalDateTime time) { PointValue pointPower = new PointValue(); pointPower.setValue(value); pointPower.setTime(time); return pointPower; } }
2、赋值,执行方法
package cn.com.easyExcel.point; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Deque; import java.util.LinkedList; import java.util.concurrent.LinkedBlockingDeque; /** * 双向队列 * **/ @Data @NoArgsConstructor public class PointValueQueue implements Serializable { private static final long serialVersionUID = 5938295366928195243L; private Long pointId; private String pointName; private LinkedBlockingDeque<PointValue> queue = new LinkedBlockingDeque<PointValue>(); public static PointValueQueue of(Long pointId, String pointName) { PointValueQueue pointValueQueue = new PointValueQueue(); pointValueQueue.setPointId(pointId); pointValueQueue.setPointName(pointName); return pointValueQueue; } /** * 新增元素, 队尾 * * @param pointValue */ public void addLast(PointValue pointValue) { this.queue.addLast(pointValue); } /** * 获取队首元素值, 不移除, 队列为空时, 抛出异常 * * @return */ @JsonIgnore public PointValue getFirst() { return this.queue.getFirst(); } /** * 获取队首元素值, 不移除, 队列为空时, 返回null * * @return */ @JsonIgnore public PointValue peekFirst() { return this.queue.peekFirst(); } /** * 获取队尾元素值, 不移除 * * @return */ @JsonIgnore public PointValue getLast() { return this.queue.getLast(); } /** * 获取队首元素值, 移除 * * @return */ public PointValue poll() { return this.queue.poll(); } /** * 获取队列当前大小 * * @return */ public int size() { return this.queue.size(); } /** * 是否为空队列 * * @return */ @JsonIgnore public boolean isEmpty() { return this.queue.isEmpty(); } /** * 清空 */ public void clear() { this.queue.clear(); } }
3、调用方法,实现过程
package cn.com.easyExcel; import cn.com.easyExcel.point.PointValue; import cn.com.easyExcel.point.PointValueQueue; import com.baomidou.mybatisplus.core.toolkit.StringPool; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * @description:测试写队列 * LinkedBlockingQueue和LinkedBlockingDeque,两个都是队列,只不过前者只能一端出一端入,后者则可以两端同时出入, * 并且都是结构改变线程安全的队列。其实两个队列从实现思想上比较容易理解, * 有以下特点: * 链表结构(动态数组) * 通过ReentrantLock实现锁 * 利用Condition实现队列的阻塞等待,唤醒 * */ public class DequeDemoTest { @Test public void LinkedBlockingDeque() { //字符串池 String key = StringPool.EMPTY; Map<Long, PointValueQueue> queueMap = getSet(); for (int i = 0; i <20 ; i++) { // 队列 PointValueQueue pointValueQueue = Optional.ofNullable(queueMap.get((long)i)).orElse(PointValueQueue.of((long)i, i+"")); System.out.println((i)+"之前::"+pointValueQueue); // 队尾元素 PointValue tail = pointValueQueue.getLast(); // 队首元素 PointValue head = pointValueQueue.getFirst(); if(i%3==0){ //pointValueQueue.clear(); // 获取队首元素值, 移除 pointValueQueue.poll(); System.out.println((i)+"之后:::::"+pointValueQueue); } if(i==10){ pointValueQueue.clear(); System.out.println((i)+"最后:::::"+pointValueQueue); } } } //设置值 private Map<Long, PointValueQueue> getSet(){ Map<Long, PointValueQueue> roomSuspectedQueue=new HashMap<Long, PointValueQueue>(); for (int i = 0; i < 20; i++) { PointValueQueue pointValueQueue = PointValueQueue.of((long)i, i + ""); for (int j = 0; j <10 ; j++) { LocalDateTime now = LocalDateTime.now(); LocalDateTime time = now.plusHours(j); //功率 BigDecimal p = new BigDecimal(""+j); // 队列新元素 PointValue pointValue = PointValue.of(p, time); // 更新队列 pointValueQueue.addLast(pointValue); } roomSuspectedQueue.put((long)i,pointValueQueue); } return roomSuspectedQueue; } }