队列
(本篇文章内容包含网上资料,如有侵权请联系博主)
一、概念
队列是一种特殊的线性表,是一种先进先出(First In First Out, FIFO)的结构,也就是说只能在表头进行删除,在表尾进行添加。Queue接口与List、Set同一级别,都是继承了Collection接口,Deque(双向队列)接口继承了Queue接口,LinkedList实现了Deque接口。
(1)、Queue的实现
- 没有实现阻塞接口
LinkedList:实现了java.util.Queue接口和java.util.AbstractQueue接口内置的
不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue类在 Collection
Framework 中加入两个具体集合实现。
PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据
它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数
的 java.util.Comparator 实现来定位。
ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不
需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知
道队列的大小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很
好。收集关于队列大小的信息会很慢,需要遍历队列。
- 实现阻塞接口
java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实
质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除
元素,线程执行操作阻塞,直到有空间或者元素可用。
五个队列所提供的各有不同:
* ArrayBlockingQueue :一个由数组支持的有界队列。
* LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
* PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
* DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
* SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集
(rendezvous)机制。
(2)、操作
- add 增加一个元索
如果队列已满,则抛出一个IIIegaISlabEepeplian异常
- remove 移除并返回队列头部的元素
如果队列为空,则抛出一个NoSuchElementException异常
- element 返回队列头部的元素
如果队列为空,则抛出一个NoSuchElementException异常
- offer 添加一个元素并返回true
如果队列已满,则返回false
- poll 移除并返问队列头部的元素
如果队列为空,则返回null
- peek 返回队列头部的元素
如果队列为空,则返回null
- put 添加一个元素
如果队列满,则阻塞
- take 移除并返回队列头部的元素
如果队列为空,则阻塞
LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。
DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
二、两种实现方式
(1)、顺序队列
顺序队列(底层都是利用数组作为容器)的实现。
最后一步操作出现队列已满的假现象,这种假现象我们称之为假溢出,之所以出现这样假溢出的现象是因为顺序表队列的存储单元没有重复利用机制,而解决该问题的最合适的方式就是将顺序队列设计为循环结构。
顺序循环队列就是将顺序队列设计为在逻辑结构上收尾相接的循环结构:
顺序队列实例:
import java.util.Arrays;
public class SequenceQueue<T> {
private int DEFAULT_SIZE = 10;
// 保存数组的长度
private int capacity;
// 定义一个数组用于保存顺序队列的元素
private Object[] elementData;
// 保存顺序队列中元素的当前个数
private int front = 0;
private int rear = 0;
// 以默认数组长度创建空顺序队列
public SequenceQueue() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
// 以一个初始化元素来创建顺序队列
public SequenceQueue(T element) {
this();
elementData[0] = element;
rear++;
}
/**
* 以指定长度的数组来创建顺序线性表
*
* @param element 指定顺序队列中第一个元素
* @param initSize 指定顺序队列底层数组的长度
*/
public SequenceQueue(T element, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = element;
rear++;
}
/**
* 获取顺序队列的大小
*
* @return 顺序队列的大小值
*/
public int length() {
return rear - front;
}
/**
* 插入队列
*
* @param element 入队列的元素
*/
public void add(T element) {
if (rear > capacity - 1) {
throw new IndexOutOfBoundsException("队列已满异常");
}
elementData[rear++] = element;
}
/**
* 移除队列
*
* @return 出队列的元素
*/
public T remove() {
if (empty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
// 保留队列的rear端的元素的值
T oldValue = (T) elementData[front];
// 释放队列顶元素
elementData[front++] = null;
return oldValue;
}
// 返回队列顶元素,但不删除队列顶元素
public T element() {
if (empty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
return (T) elementData[front];
}
// 判断顺序队列是否为空
public boolean empty() {
return rear == front;
}
// 清空顺序队列
public void clear() {
// 将底层数组所有元素赋值为null
Arrays.fill(elementData, null);
front = 0;
rear = 0;
}
public String toString() {
if (empty()) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (int i = front; i < rear; i++) {
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
// 测试类
public class SequenceQueueTest {
public static void main(String[] args) {
SequenceQueue<String> queue = new SequenceQueue<String>();
// 依次将4个元素加入到队列中
queue.add("aaaa");
queue.add("bbbb");
queue.add("cccc");
queue.add("dddd");
System.out.println(queue);
System.out.println("访问队列的front端元素:" + queue.element());
System.out.println("第一次弹出队列的front端元素:" + queue.remove());
System.out.println("第二次弹出队列的front端元素:" + queue.remove());
System.out.println("两次remove之后的队列:" + queue);
}
}
// 结果
[dddd, cccc, bbbb, aaaa]
访问栈顶元素:dddd
第一次弹出栈顶元素:dddd
第二次弹出栈顶元素:cccc
两次pop之后的栈:[bbbb, aaaa]
访问栈顶元素:dddd
第一次弹出栈顶元素:dddd
第二次弹出栈顶元素:cccc
两次pop之后的栈:[bbbb, aaaa]
顺序循环队列实例:
import java.io.Serializable;
import java.util.NoSuchElementException;
public class SeqQueue<T> implements Queue<T> ,Serializable {
private static final long serialVersionUID = -1664818681270068094L;
private static final int DEFAULT_SIZE = 10;
private T elementData[];
private int front,rear;
private int size;
public SeqQueue(){
elementData= (T[]) new Object[DEFAULT_SIZE];
front=rear=0;
}
public SeqQueue(int capacity){
elementData= (T[]) new Object[capacity];
front=rear=0;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return front==rear;
}
/**
* data 入队,添加成功返回true,否则返回false,可扩容
* @param data
* @return
*/
@Override
public boolean add(T data) {
//判断是否满队
if (this.front==(this.rear+1)%this.elementData.length){
ensureCapacity(elementData.length*2+1);
}
//添加data
elementData[this.rear]=data;
//更新rear指向下一个空元素的位置
this.rear=(this.rear+1)%elementData.length;
size++;
return true;
}
/**
* offer 方法可插入一个元素,这与add 方法不同,
* 该方法只能通过抛出未经检查的异常使添加元素失败。
* 而不是出现异常的情况,例如在容量固定(有界)的队列中
* NullPointerException:data==null时抛出
* IllegalArgumentException:队满,使用该方法可以使Queue的容量固定
* @param data
* @return
*/
@Override
public boolean offer(T data) {
if (data==null)
throw new NullPointerException("The data can\'t be null");
//队满抛出异常
if (this.front==(this.rear+1)%this.elementData.length){
throw new IllegalArgumentException("The capacity of SeqQueue has
reached its maximum");
}
//添加data
elementData[this.rear]=data;
//更新rear指向下一个空元素的位置
this.rear=(this.rear+1)%elementData.length;
size++;
return true;
}
/**
* 返回队头元素,不执行删除操作,若队列为空,返回null
* @return
*/
@Override
public T peek() {
return elementData[front];
}
/**
* 返回队头元素,不执行删除操作,若队列为空,抛出异常:
NoSuchElementException
* @return
*/
@Override
public T element() {
if(isEmpty()){
throw new NoSuchElementException("The SeqQueue is empty");
}
return peek();
}
/**
* 出队,执行删除操作,返回队头元素,若队列为空,返回null
* @return
*/
@Override
public T poll() {
T temp=this.elementData[this.front];
this.front=(this.front+1)%this.elementData.length;
size--;
return temp;
}
/**
* 出队,执行删除操作,若队列为空,抛出异常:NoSuchElementException
* @return
*/
@Override
public T remove() {
if (isEmpty()){
throw new NoSuchElementException("The SeqQueue is empty");
}
return poll();
}
@Override
public void clearQueue() {
for (int i=this.front; i!=this.rear ; i=(i+1)%elementData.length) {
elementData[i] = null;
}
//复位
this.front=this.rear=0;
size=0;
}
/**
* 扩容的方法
* @param capacity
*/
public void ensureCapacity(int capacity) {
//如果需要拓展的容量比现在数组的容量还小,则无需扩容
if (capacity<size)
return;
T[] old = elementData;
elementData= (T[]) new Object[capacity];
int j=0;
//复制元素
for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) {
elementData[j++] = old[i];
}
//恢复front,rear指向
this.front=0;
this.rear=j;
}
}
(2)、链式队列
链式队列,将使用带头指针front和尾指针rear的单链表实现,front直接指向
队头的第一个元素,rear指向队尾的最后一个元素。
public class LinkQueue<T> {
// 定义一个内部类Node,Node实例代表链队列的节点
private class Node {
// 保存节点的数据
private T data;
// 指向下个节点的引用
private Node next;
// 无参构造器
public Node() {
}
// 初始化全部属性的构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
// 保存该链队列的头节点
private Node front;
// 保存该链队列的尾节点
private Node rear;
// 保存该链队列中已包含的节点数
private int size;
// 创建空链队列
public LinkQueue() {
// 空链队列,front和rear的值都为null
front = null;
rear = null;
}
// 以指定数据元素来创建链队列,该链队列只有一个元素
public LinkQueue(T element) {
front = new Node(element, null);
// 只有一个节点,front、rear都是指向该节点
rear = front;
size++;
}
// 返回链队列的长度
public int length() {
return size;
}
// 将新元素加入队列
public void add(T element) {
// 如果该链队列还是空链队列
if (front == null) {
front = new Node(element, null);
// 只有一个节点,front、rear都是指向该节点
rear = front;
} else {
// 创建新节点
Node newNode = new Node(element, null);
// 让尾节点的next指向新增的节点
rear.next = newNode;
rear = newNode;
}
size++;
}
// 删除队列front端的元素
public T remove() {
Node oldfront = front;
// 让front引用指向原队列顶元素的下一个元素
front = front.next;
// 释放原队列顶元素的next引用
oldfront.next = null;
size--;
return oldfront.data;
}
// 访问队列顶元素,但不删除队列顶元素
public T element() {
return rear.data;
}
// 判断链队列是否为空队列
public boolean empty() {
return size == 0;
}
// 请空链队列
public void clear() {
// 将front、rear两个节点赋为null
front = null;
rear = null;
size = 0;
}
public String toString() {
// 链队列为空队列时
if (empty()) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = front; current != null; current = current.next) {
sb.append(current.data.toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
// 测试类
public class LinkQueueTest {
public static void main(String[] args) {
LinkQueue<String> queue = new LinkQueue<String>("aaaa");
// 依次将4个元素加入到队列中
queue.add("bbbb");
queue.add("cccc");
queue.add("dddd");
System.out.println(queue);
// 删除一个元素后
queue.remove();
System.out.println("删除一个元素后的队列:" + queue);
// 再添加一个元素
queue.add("eeee");
System.out.println("再次添加元素后的队列:" + queue);
}
}
// 结果
[aaaa, bbbb, cccc, dddd]
删除一个元素后的队列:[bbbb, cccc, dddd]
再次添加元素后的队列:[bbbb, cccc, dddd, eeee]
删除一个元素后的队列:[bbbb, cccc, dddd]
再次添加元素后的队列:[bbbb, cccc, dddd, eeee]
三、队列实际应用场景
- 模拟现实世界中的队列,如售票柜台的队列以及其他先到先服务的场景。
- 计算客户在呼叫中心等待的时间。
- 异步数据的传输(文件输入输出、管道、嵌套字)。
- 操作系统中的优先级任务执行。
- 短信群体发送 应用的发布订阅模式。