什么是队列
队列(Queue),设计用于在处理之前保存元素的集合。除了基本的Collection操作之外,队列还提供了额外的插入、提取和检查操作。这些方法中的每一个都以两种形式存在:一种在操作失败时抛出异常,另一种返回特殊值( null或false ,具体取决于操作)。
方法函数
抛出异常 | 返回false或null | |
插入 | boolean add(e) | boolean offer(e) |
消除 | E remove() | E poll() |
检查 | E element() | E peek() |
- add(e) / offer(e):在队尾添加元素,当队列存储满了,add(e)会抛出异常,offer(e)返回false。
- remove() / poll():移除队尾的元素并返回,当队列为空时,remove()抛出异常,poll()返回null。
- element() / peek():获取队尾的元素,当队列为空时,element()抛出异常,peek()返回null。
当队列可以存储特殊值,例如null。因为poll()和peek()在队列空时会返回null,造成无法确定队列是否还有元素,这个时候最好用可以抛出异常的方法来区分。
普通队列
LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}
public interface Queue<E> extends Collection<E> {}
LinkedList实现了Deque双端队列,而双端队列继承了Queue,所以LinkedList不仅有集合的特性,也有队列的特性。
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("A");
String str = queue.element();
System.out.println(str); // A
System.out.println(queue); // [A]
queue.remove();
System.out.println(queue); // []
String str2 = queue.element(); // java.util.NoSuchElementException
}
当队列里面没有数据的时候,element()会抛出异常。
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("A");
String str = queue.peek();
System.out.println(str); // A
System.out.println(queue); // [A]
queue.poll();
System.out.println(queue); // []
String str2 = queue.peek(); // null
System.out.println(str2);
}
当队列里面没有数据的时候,peek()不会抛出异常,直接返回null。
PriorityQueue
优先级队列
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
/**
* 默认初始容量
*/
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* 队列存储数组
* 优先级队列表示为平衡二叉堆:queue[n] 的两个孩子是 queue[2n+1] 和 queue[2(n+1)]。
* 优先级队列按比较器排序,如果比较器为空,则按元素的自然顺序排序:
* 对于堆中的每个节点 n 和 n 的每个后代 d,n <= d。
* 具有最低值的元素在 queue[0] 中,假设队列是非空的。
*/
transient Object[] queue;
/**
* 队列的大小
*/
private int size = 0;
/**
* 比较器
*/
private final Comparator<? super E> comparator;
/**
* 无参构造器,调用两个参数的构造器
*/
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
/**
* 指定初始容量构造器,调用两个参数的构造器
*/
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;
}
...略
}
添加元素
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
- 当队列达到当前存储的最大容量,调用grow(i+1)方法进行扩容
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
判断是否oldCapacity < 64,结果为true 则扩容oldCapacity + (oldCapacity + 2),即2*(oldCapacity +1) 加倍扩容。结果为false 则扩容oldCapacity + (oldCapacity >> 1),即oldCapacity + 0.5*oldCapacity,扩容50%。
- suftUp()判断是否有比较器调用不同的方法
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
当优先级队列没有指定比较器的时候,默认使用Comparable的compareTo方法进行比较。指定比较器Comparator则用比较器的compare方法进行比较。
例子
- 未指定比较器
@Data
public class Gen {
private int age;
public Gen(int age) {
this.age = age;
}
public static void main(String[] args) {
Queue<Integer> queue = new PriorityQueue<>();
queue.add(100);
queue.add(80);
queue.add(90);
System.out.println(queue); // [80, 100, 90]
System.out.println(queue.peek()); // 80
}
}
new PriorityQueue<>()未指定比较器,默认调用siftUpComparable(int k, E x)方法。
@Data
public class Gen {
private int age;
public Gen(int age) {
this.age = age;
}
public static void main(String[] args) {
Queue<Gen> queue = new PriorityQueue<>();
Gen gen1 = new Gen(100);
Gen gen2 = new Gen(60);
Gen gen3 = new Gen(90);
/*
* java.lang.ClassCastException:
* com.xx.xx.Gen cannot be cast to java.lang.Comparable
*/
queue.add(gen1);
queue.add(gen2);
queue.add(gen3);
System.out.println(queue);
System.out.println(queue.peek());
}
}
ClassCastException类转换异常,因为siftUpComparable(_int _k, E x)方法中进行了强转Comparable<? _super _E> key = (Comparable<? _super _E>) x,类Gen没有实现Comparable接口。
@Data
public class Gen implements Comparable<Gen>{
private int age;
public Gen(int age) {
this.age = age;
}
@Override
public int compareTo(Gen o) {
return this.getAge() - o.getAge();
}
public static void main(String[] args) {
Queue<Gen> queue = new PriorityQueue<>();
Gen gen1 = new Gen(100);
Gen gen2 = new Gen(60);
Gen gen3 = new Gen(90);
queue.add(gen1);
queue.add(gen2);
queue.add(gen3);
System.out.println(queue); // [Gen(age=60), Gen(age=100), Gen(age=90)]
System.out.println(queue.peek()); // Gen(age=60)
}
}
实现Comparable接口并重写compareTo方法后可以正常执行。‘
- 指定比较器
/**
* 自定义比较器
*/
public class GenComparator implements Comparator<Gen> {
@Override
public int compare(Gen o1, Gen o2) {
return o1.getAge() - o2.getAge();
}
}
@Data
public class Gen {
private int age;
public Gen(int age) {
this.age = age;
}
public static void main(String[] args) {
Queue<Gen> queue = new PriorityQueue<>(new GenComparator());
Gen gen1 = new Gen(100);
Gen gen2 = new Gen(60);
Gen gen3 = new Gen(90);
queue.add(gen1);
queue.add(gen2);
queue.add(gen3);
System.out.println(queue); // [Gen(age=60), Gen(age=100), Gen(age=90)]
System.out.println(queue.peek()); // Gen(age=60)
}
}
自定义GenComparator实现Comparator接口。添加元素的时候调用siftUpUsingComparator(_int _k, E x)接口,使用自定义比较器进行比较。
阻塞队列
支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用的操作。
BlockingQueue
抛出异常 | 返回特殊值 | 阻塞 | 定时 | |
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | — | — |
public interface BlockingQueue<E> extends Queue<E> {}
BlockingQueue接口继承Queue,所以有Queue的方法,并且拥有特有的阻塞方法和定时方法。
ArrayBlockingQueue
由数组支持的有界阻塞队列。此队列对元素进行 FIFO(先进先出)排序。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。
这是一个经典的“有界缓冲区”,其中一个固定大小的数组保存由生产者插入并由消费者提取的元素。一旦创建,容量将无法更改。尝试put元素放入完整队列将导致操作阻塞;尝试从空队列中take元素同样会阻塞。
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/**
* 创建具有给定(固定)容量和指定访问策略的ArrayBlockingQueue
*/
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
/**
* 创建具有给定(固定)容量和指定访问策略的ArrayBlockingQueue
* fair: 指定是否公平锁,如果为true ,则在插入或删除时阻塞的线程的队列访问将按 FIFO
* 顺序处理;如果为false ,则未指定访问顺序。
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
/**
* 创建具有给定(固定)容量、指定访问策略并最初包含给定集合的元素的ArrayBlockingQueue
* ,按集合迭代器的遍历顺序添加。
*/
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
}
LinkedBlockingQueue
基于链接节点的可选有界阻塞队列。此队列对元素进行 FIFO(先进先出)排序。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。链接队列通常比基于数组的队列具有更高的吞吐量,但在大多数并发应用程序中性能更不可预测。
可选的容量绑定构造函数参数用作防止过度队列扩展的一种方式。容量(如果未指定)等于Integer.MAX_VALUE 。链接节点在每次插入时动态创建,除非这会使队列超出容量。
PriorityBlockingQueue
使用与类PriorityQueue相同的排序规则并提供阻塞检索操作的无界阻塞队列。虽然此队列在逻辑上是无界的,但尝试添加可能会由于资源耗尽而失败(导致OutOfMemoryError )。此类不允许null元素。依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做会导致ClassCastException )。