哲学三问
队列是什么
队列是一种重要的抽象数据结构,可类比于生活中的排队场景Java 语言提供了队列的支持,内置了多种类型的队列供我们使用 队列的特点是先进先出。队列的用处很大
为什么要使用队列
例如:
消息队列是用来解决这样的问题的:将突发的大量请求转换为服务器能够处理的队列请求。eg:在一个秒杀活动中,服务器1秒可以处理100条请求。而在秒杀活动开启时1秒进来1000个请求并且持续10秒。这个时候就需要将这10000个请求放入消息队列里面,后端按照原来的能力处理,用100秒将队列中的请求处理完毕。这样就不会导致宕机
java中如何使用队列
单向队列
单向队列比较简单,只能向队尾添加元素,从队头删除元素。比如最典型的排队买票例子,新来的人只能在队列后面,排到最前边的人才可以买票,买完了以后,离开队伍。这个过程是一个非常典型的队列。
Java 定义了队列的基本操作,接口类型为 java.util.Queue,接口定义如下所示。Queue 定义了两套队列操作方法:
-
add、remove、element 操作失败抛出异常;
-
offer 操作失败返回 false 或抛出异常,poll、peek 操作失败返回 null;
public interface Queue<E> extends Collection<E> {
//插入元素,成功返回true,失败抛出异常
boolean add(E e);
//插入元素,成功返回true,失败返回false或抛出异常
boolean offer(E e);
//取出并移除头部元素,空队列抛出异常
E remove();
//取出并移除头部元素,空队列返回null
E poll();
//取出但不移除头部元素,空队列抛出异常
E element();
//取出但不移除头部元素,空队列返回null
E peek();
}
双向队列
如果一个队列的头和尾都支持元素入队,出队,那么这种队列就称为双向队列,英文是Deque,可以通过java.util.Deque来查看Deque的接口定义,Deque 也同样定义了两套队列操作方法,针对头部操作方法为 xxxFirst、针对尾部操作方法为 xxxLast:
-
add、remove、get 操作失败抛出异常;
-
offer 操作失败返回 false 或抛出异常,poll、peek 操作失败返回 null;
-
Deque 另外还有 removeFirstOccurrence、removeLastOccurrence 方法用于删除指定元素,元素存在则删除,不存在则队列不变。
public interface Deque<E> extends Queue<E> {
//插入元素到队列头部,失败抛出异常
void addFirst(E e);
//插入元素到队列尾部,失败抛出异常
void addLast(E e);
//插入元素到队列头部,失败返回false或抛出异常
boolean offerFirst(E e);
//插入元素到队列尾部,失败返回false抛出异常
boolean offerLast(E e);
//取出并移除头部元素,空队列抛出异常
E removeFirst();
//取出并移除尾部元素,空队列抛出异常
E removeLast();
//取出并移除头部元素,空队列返回null
E pollFirst();
//取出并移除尾部元素,空队列返回null
E pollLast();
//取出但不移除头部元素,空队列抛出异常
E getFirst();
//取出但不移除尾部元素,空队列抛出异常
E getLast();
//取出但不移除头部元素,空队列返回null
E peekFirst();
//取出但不移除尾部元素,空队列返回null
E peekLast();
//移除指定头部元素,若不存在队列不变,移除成功返回true
boolean removeFirstOccurrence(Object o);
//移除指定尾部元素,若不存在队列不变,移除成功返回true
boolean removeLastOccurrence(Object o);
//单向队列方法,参考Queue
//栈方法,参考栈
//集合方法,参考集合定义
}
队列的具体实现
通常情况下,队列有数组和链表两种实现方式。
-
采用链表实现的队列,没有个数限制。插入元素时直接接在链表的尾部,取出元素时直接从链表的头部取出即可。
-
采用数组实现的队列,通常是循环数组,受限于数组的大小,存在天然的个数上限。插入和取出元素时,必须采用队列头部指针和队列尾部指针进行队列满和队列空的判断。
单向队列
- LinkedList基于链表的实现方式是线程不安全的
- PriorityQueue无界的优先级队列,保存队列元素的顺序不是按照及加入队列的顺序,而是按照队列元素的大小进行重新排序。因此当调用peek()或pool()方法取出队列中头部的元素时,并不是取出最先进入队列的元素,而是取出队列的最小元素。
我们刚刚才说到队列的特点是先进先出,为什么这里就按照大小顺序排序了呢?我们还是先看一下它的介绍,直接翻译过来:
基于优先级堆的无界的优先级队列。 PriorityQueue的元素根据自然排序进行排序,或者按队列构建时提供的 Comparator进行排序,具体取决于使用的构造方法。 优先队列不允许 null 元素。 通过自然排序的PriorityQueue不允许插入不可比较的对象。 该队列的头是根据指定排序的最小元素。 如果多个元素都是最小值,则头部是其中的一个元素——任意选取一个。 队列检索操作poll、remove、peek和element访问队列头部的元素。 优先队列是无界的,但有一个内部容量,用于管理用于存储队列中元素的数组的大小。 基本上它的大小至少和队列大小一样大。 当元素被添加到优先队列时,它的容量会自动增长。增长策略的细节没有指定。
一句话概括,PriorityQueue使用了一个高效的数据结构:堆。底层是使用数组保存数据。还会进行排序,优先将元素的最小值存到队头
PriorityQueue 本质也是一个动态数组,在这一方面与ArrayList是一致的
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
- PriorityQueue调用默认的构造方法时,使用默认的初始容量(DEFAULT_IITIAL_CAPACITY = 11)创建一个PriorityQueue,并根据其自然顺序来排序其元素(使用加入其中的集合元素实现的Comparable)。
- 当使用指定容量的构造方法时,使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序来排序其元素(使用加入其中的集合元素实现的Comparable)
- 当使用指定的初始容量创建一个 PriorityQueue,并根据指定的比较器comparator来排序其元素。当添加元素到集合时,会先检查数组是否还有余量,有余量则把新元素加入集合,没余量则调用 grow()方法增加容量,然后调用siftUp将新加入的元素排序插入对应位置。 除了这些,还要注意的是: 1.PriorityQueue不是线程安全的。如果多个线程中的任意线程从结构上修改了列表, 则这些线程不应同时访问 PriorityQueue 实例,这时请使用线程安全的PriorityBlockingQueue 类。 2.不允许插入 null 元素。 3.PriorityQueue实现插入方法(offer、poll、remove() 和 add 方法) 的时间复杂度是O(log(n)) ;实现 remove(Object) 和 contains(Object) 方法的时间复杂度是O(n) ;实现检索方法(peek、element 和 size)的时间复杂度是O(1)。所以在遍历时,若不需要删除元素,则以peek的方式遍历每个元素。 4.方法iterator()中提供的迭代器并不保证以有序的方式遍历PriorityQueue中的元素。
- ConcurrentLinkedQueue****并发队列是一个基于链表实现的线程安全的无界队列。内部元素按先进先出的顺序排序,最后插入的元素位于队列尾部。ConcurrentLinkedQueue 不允许插入 null。当有多个线程同时访问队列时,此队列是一个比较合适的选择。
Queue 接口的子接口 BlockingQueue 定义了阻塞队列。阻塞队列访问队列元素时,可能会一直阻塞直到操作完成,比如从空队列中取元素、向满队列中插入元素等,主要有延时队列、同步队列、链表阻塞队列、数组阻塞队列、优先级阻塞队列。
双向队列
Deque 接口的实现相对较少,主要有 LinkedList、ArrayDeque、ConcurrentLinkedDeque 和 LinkedBlockingDeque。
