原文连接:wangwei.one/posts/java-…html
前面,咱们学习了 栈的实现及应用 ,本篇咱们来学习一下最后一种线性表——队列。java
队列是咱们平常开发中常常会用到的一种数据结构,咱们常常使用队列进行异步处理、系统解耦、数据同步、流量削峰、缓冲、限流等。例如,不是全部的业务都必须实时处理、不是全部的请求都必须实时反馈结果给用户、不是全部的请求都必须100%处理成功、不知道谁依赖“我”的处理结果、不关心其余系统如何处理后续业务、不须要强一致性,只需保证最终一致性便可、想要保证数据处理的有序性等等,这些问题都考虑使用队列来解决。git
队列与 栈 同样,都是操做受限的线性表数据结构。队列从一端插入数据,而后从另外一端取出数据。插入数据的一端称为"队尾",取出数据的一端称为"队头",如图所示:github
与 栈 同样,队列也分为顺序队列与链式队列,分别使用数组与链表来实现。算法
链式队列实现比较简单,使用单链表便可实现,若是所示:数组
package one.wangwei.algorithms.datastructures.queue.impl;
import one.wangwei.algorithms.datastructures.queue.IQueue;
import java.util.NoSuchElementException;
/** * 链表队列 * * @param <T> * @author https://wangwei.one * @date 2019/03/27 */
public class LinkedQueue<T> implements IQueue<T> {
private int size = 0;
private Node<T> head;
private Node<T> tail;
public LinkedQueue() {
}
/** * 添加元素到队列头部 * * @param value * @return */
@Override
public boolean offer(T value) {
Node<T> last = tail;
Node<T> newNode = new Node<>(value, null);
tail = newNode;
if (last == null) {
head = newNode;
} else {
last.next = newNode;
}
size++;
return true;
}
/** * 移除队列尾部元素 * * @return */
@Override
public T poll() {
if (head == null) {
throw new NoSuchElementException("Queue underflow");
}
Node<T> tmpHead = head;
head = head.next;
tmpHead.next = null;
size--;
if (head == null) {
tail = null;
}
return tmpHead.element;
}
/** * 查看队列尾部元素值 * * @return */
@Override
public T peek() {
if (head == null) {
throw new NoSuchElementException("Queue underflow");
}
return head.element;
}
/** * 清除队列元素 */
@Override
public void clear() {
for (Node<T> x = head; x != null; ) {
Node<T> next = x.next;
x.element = null;
x.next = null;
x = next;
}
head = tail = null;
size = 0;
}
/** * 队列大小 */
@Override
public int size() {
return size;
}
/** * Node * * @param <T> */
private static class Node<T> {
private T element;
private Node<T> next;
private Node(T element) {
this.element = element;
}
private Node(T element, Node<T> next) {
this.element = element;
this.next = next;
}
}
}
复制代码
源码数据结构
基于链表的实现方式,能够实现一个支持无限排队的无界队列(unbounded queue),可是可能会致使过多的请求排队等待,请求处理的响应时间过长。因此,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。异步
顺序队列采用数组实现,数组的实现有两种方式,一种是顺序式的,一种是循环数组实现。ide
当队列尾部没有剩余空间后,须要集中进行一次数据搬迁腾出空间,才能继续进行入队操做。如图所示:post
顺序队列会存在数据搬迁的问题,对入队操做有性能方面的影响。咱们能够采用循环数组的方式来解决这一问题,如图所示:
当队尾无存储空间且队列未满时,咱们能够将其存储到数组的前半部分剩余的空间去。
循环队列的实现关键在于队列为空和为满时的状态判断:
rear == front
front == (rear + 1) % array.length
,队满时,会浪费一个数组的存储空间。代码以下:
package one.wangwei.algorithms.datastructures.queue.impl;
import one.wangwei.algorithms.datastructures.queue.IQueue;
import java.util.NoSuchElementException;
/** * 数组队列 * * @param <T> * @author https://wangwei.one * @date 2019/02/04 */
public class ArrayQueue<T> implements IQueue<T> {
/** * default array size */
private static final int DEFAULT_SIZE = 1024;
/** * 元素数组 */
private T[] array;
/** * 队头指针下标 */
private int front = 0;
/** * 队尾指针下标 */
private int rear = 0;
public ArrayQueue() {
this(DEFAULT_SIZE);
}
public ArrayQueue(int capacity) {
array = (T[]) new Object[capacity];
}
/** * 添加队尾元素 * * @param value * @return */
@Override
public boolean offer(T value) {
if (isFull()) {
grow();
}
array[rear % array.length] = value;
rear++;
return true;
}
/** * grow queue size doubly */
private void grow() {
int growSize = array.length << 1;
T[] tmpArray = (T[]) new Object[growSize];
int adjRear = rear % array.length;
int endIndex = rear > array.length ? array.length : rear;
if (adjRear < front) {
System.arraycopy(array, 0, tmpArray, array.length - adjRear, adjRear + 1);
}
System.arraycopy(array, front, tmpArray, 0, endIndex - front);
array = tmpArray;
rear = (rear - front);
front = 0;
}
/** * 移除队头元素 * * @return */
@Override
public T poll() {
if (isEmpty()) {
throw new NoSuchElementException("Queue underflow");
}
T element = array[front % array.length];
array[front % array.length] = null;
front++;
if (isEmpty()) {
// remove last element
front = rear = 0;
}
int shrinkSize = array.length >> 1;
if (shrinkSize >= DEFAULT_SIZE && size() < shrinkSize) {
shrink();
}
return element;
}
/** * 压缩 */
private void shrink() {
int shrinkSize = array.length >> 1;
T[] tmpArray = (T[]) new Object[shrinkSize];
int adjRear = rear % array.length;
int endIndex = rear > array.length ? array.length : rear;
if (adjRear <= front) {
System.arraycopy(array, 0, tmpArray, array.length - front, adjRear);
}
System.arraycopy(array, front, tmpArray, 0, endIndex - front);
array = null;
array = tmpArray;
rear = rear - front;
front = 0;
}
/** * 查看队头元素 * * @return */
@Override
public T peek() {
if (isEmpty()) {
throw new NoSuchElementException("Queue underflow");
}
return array[front % array.length];
}
/** * 清除队列元素 */
@Override
public void clear() {
array = null;
front = rear = 0;
}
/** * 队列大小 */
@Override
public int size() {
return rear - front;
}
/** * 判断队列是否满 * * @return */
private boolean isFull() {
return !isEmpty() && (front == (rear + 1) % array.length);
}
/** * 判断队是否为空 * * @return */
private boolean isEmpty() {
return size() <= 0;
}
}
复制代码
基于数组实现的有界队列(bounded queue),队列的大小有限,当请求数量超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来讲,就相对更加合理。不过,设置一个合理的队列大小,也是很是有讲究的。队列太大致使等待的请求太多,队列过小会致使没法充分利用系统资源、发挥最大性能。