什么是队列
栈是先进者后出,后进者先出,而队列是先进者先出,对于在内存中如何存储并没有要求,如果通过数组来实现叫做顺序队列,如果通过链表实现,则叫做链式队列,可以看出上图就是顺序队列,就好比地铁过安检时,每个人排队,依次进入检查
顺序队列
队列的特点是先进者先出,所以需要两个指针,一个指针指向第一条数据,另一个指向最后一条数据
因为数组创建时便已经指定大小,所以顺序队列的存储空间有限,为了提高空间的利用率,每当有出对操作时,我们可以进行数据搬移
但是如果频繁的进行数据搬移,会使得性能降低,所以当没有空闲空间时,我们只需要在入队时,集中触发一次数据的搬移操作即可.
public class ArrayQueue<E> {
// 数组:items,数组大小:n
private E[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public ArrayQueue(int capacity) {
items = (E[])new Object[capacity];
n = capacity;
}
// 入队操作,将item放入队尾
public boolean enqueue(E item) {
// tail == n表示队列末尾没有空间了
if (tail == n) {
// tail ==n && head==0,表示整个队列都占满了
if (head == 0) return false;
// 数据搬移
for (int i = head; i < tail; ++i) {
items[i-head] = items[i];
}
// 搬移完之后重新更新head和tail
tail -= head;
head = 0;
}
items[tail] = item;
++tail;
return true;
}
// 出队
public E dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
E ret = items[head];
++head;
return ret;
}
}
循环队列
上面使用数组实现了队列的基本操作,但是总会涉及到搬移数据,这样入队操作的性能就会受到影响,那有没有办法避免数据搬移数据呢?
从上图我们可以看出这是一个大小为9,head=1,tail=5的队列,再有数据入队时,将会放到下标为5的位置,tail+1,但是当tail=8时,如何处理呢?
从图中我们可以很明显的看出当有数据入队时,数据放到下标为8的位置,而tail则要更新为0,转化为代码时,我们要如何判断呢?
使用数组实现队列时,个人觉得:最关键的是,确定队空和队满的判定条件.
在用数组实现的非循环队列中,队满的判断条件是 tail == n,队空的判断条件是 head == tail。那针对循环队列,如何判断队空和队满呢?
队列为空的判断条件仍然是 head == tail。但队列满的判断条件是什么呢?
在一般情况下,队满的判断条件为 tial + 1 = head
但是有个特殊情况, head = 0, tail = n-1, 如下图所示
综上所述:
当head !=0时,
tail+1=head
当head =0时,
tail+1=n
因为tail+1的最大值为n
所以
(tail+1)%n=head
代码实现如下
public class CircularQueue<E> {
// 数组:items,数组大小:n
private E[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public CircularQueue(int capacity) {
items = (E[])new Object[capacity];
n = capacity;
}
// 入队
public boolean enqueue(E item) {
// 队列满了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出队
public E dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
E ret = items[head];
head = (head + 1) % n;
return ret;
}
}
链表队列
public class QueueByList<E> {
// 队列的队首和队尾
private Node head = null;
private Node tail = null;
// 入队
public void enqueue(E value) {
if (tail == null) {
Node newNode = new Node(value, null);
head = newNode;
tail = newNode;
} else {
tail.next = new Node(value, null);
tail = tail.next;
}
}
// 出队
public E dequeue() {
if (head == null) return null;
E value = (E) head.data;
head = head.next;
if (head == null) {
tail = null;
}
return value;
}
//结点
private static class Node<E> {
private E data;
private Node next;
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
public E getData() {
return data;
}
}
}
阻塞队列
阻塞队列是一个支持两个附加操作的队列。
- 当队列满时,队列会阻塞插入元素的线程,直到队列不满。
- 当队列为空时,获取元素的线程会等待队列变为非空
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
JDK7提供了7个阻塞队列。分别是
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。