1. 概述
队列是“先进先出”(FIFO,First InFirst Out)的数据结构。队列的概念就好比乘火车时候买票的队伍,先到的人当然可以优先买票,买完后就从前端离去准备乘火车,而队尾的后端又陆续有新的乘客加入。
它有下列特性:
a. 先进先出(FIFO)。
b. 队列只允许在"队首"进行删除操作,而在"队尾"进行插入操作。 队列通常包括的两种操作:入队列 和 出队列。
队列的示意图:(图片来自百度图片)
队列分为:
①单向队列:只能在一堆插入数据,另一端删除数据。
②双向队列:每一端都可以进行插入数据和删除数据操作。
2. 队列的实现
以下例子使用数组实现单向队列,能存储int类型的数据。
package zzw.cn.queue;
/**
* @author 鹭岛猥琐男
* @create 2019/8/13 22:59
*/
public class ArrayQueue
{
private int[] mArray;
private int mCount;
/* 构造方法 */
public ArrayQueue(int size)
{
mArray = new int[size];
mCount = 0;
}
// 将value添加到队列的末尾
public void add(int value)
{
mArray[mCount++] = value;
}
// 返回队首元素值,并删除队首元素
public int pop()
{
int ret = mArray[0];
mCount--;
for (int i = 1; i <= mCount; i++)
mArray[i - 1] = mArray[i];
return ret;
}
// 返回队列的大小
public int size()
{
return mCount;
}
// 返回队列是否为空
public boolean isEmpty()
{
return size() == 0;
}
}
测试队列:
package zzw.cn.queue;
/**
* @author 鹭岛猥琐男
* @create 2019/8/13 22:57
*/
public class TestQueue
{
public static void main(String[] args)
{
ArrayQueue queue = new ArrayQueue(10);
//往队列中添加元素
for (int i = 0; i < 10; i++)
{
queue.add(i);
}
System.out.println("队列添加元素后,队列的长度为:" + queue.size());
//从队首中逐一取出元素
System.out.println("逐一取出元素:");
while (!queue.isEmpty())
{
System.out.print(queue.pop() + " ");
}
//再次往队列中添加元素
for (int i = 0; i < 10; i++)
{
queue.add(i + 10);
}
System.out.println("\n队列添加元素后,队列的长度为:" + queue.size());
}
}
测试结果:
队列添加元素后,队列的长度为:10
逐一取出元素:
0 1 2 3 4 5 6 7 8 9
队列添加元素后,队列的长度为:10
从以上结果,此队列是属于先进先出的。
3. 双向队列
在上面中,模拟了单向队列的实现。对于双向队列,在JDK中已经有相应的API实现: ArrayDeque。ArrayDeque 是 Deque 接口的一种具体实现,是依赖于可变数组来实现的。ArrayDeque 没有容量限制,可根据需求自动进行扩容。ArrayDeque不支持值为 null 的元素。
Deque 接口继承自 Queue接口,Queue 也是 Java 集合框架中定义的一种接口,直接继承自 Collection 接口。Queue 是按照先进先出(FIFO)的方式来管理其中的元素的。
Queue的源码:
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
从以上代码可以看出,Queue是一个接口,并且继承自 Collection 接口。
继续看 Deque 的源码:
package java.util;
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
void push(E e);
E pop();
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
从以上代码可以看出,Deque也是一个接口,并且继承自 Queue接口。
继续看 ArrayDeque的源码:
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
...
...
}
从此段伪代码可以看出,ArrayDeque 实现了接口 Deque。
ArrayDeque 的构造方法:
/**
* Constructs an empty array deque with an initial capacity
* sufficient to hold 16 elements.
*/
public ArrayDeque() {
elements = new Object[16];
}
/**
* Constructs an empty array deque with an initial capacity
* sufficient to hold the specified number of elements.
*
* @param numElements lower bound on initial capacity of the deque
*/
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
/**
* Constructs a deque containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator. (The first element returned by the collection's
* iterator becomes the first element, or <i>front</i> of the
* deque.)
*
* @param c the collection whose elements are to be placed into the deque
* @throws NullPointerException if the specified collection is null
*/
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
这里只分析下第一个构造方法,在此方法中,创建了大小为16的 Object 类型的数组。
底层结构:
/**
* The array in which the elements of the deque are stored.
* The capacity of the deque is the length of this array, which is
* always a power of two. The array is never allowed to become
* full, except transiently within an addX method where it is
* resized (see doubleCapacity) immediately upon becoming full,
* thus avoiding head and tail wrapping around to equal each
* other. We also guarantee that all array cells not holding
* deque elements are always null.
*/
transient Object[] elements; // non-private to simplify nested class access
/**
* The index of the element at the head of the deque (which is the
* element that would be removed by remove() or pop()); or an
* arbitrary number equal to tail if the deque is empty.
*/
transient int head;
/**
* The index at which the next element would be added to the tail
* of the deque (via addLast(E), add(E), or push(E)).
*/
transient int tail;
/**
* The minimum capacity that we'll use for a newly created deque.
* Must be a power of 2.
*/
private static final int MIN_INITIAL_CAPACITY = 8;
ArrayDeque 底部是使用数组存储元素,同时还使用了两个索引来表征当前数组的状态,分别是 head 和 tail。head 是头部元素的索引,但注意 tail 不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。
添加元素的方法:
这三个方法的源码如下:
public boolean add(E e) {
addLast(e);
return true;
}
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
在以上方法中,add 方法最终调用到 addFirst 方法,支持从队列的头部和尾部添加元素。这里分析下向末尾添加元素的方法 addLast:
这段代码中,当传入的 E e为空时候,则抛出 NullPointerException;如果不为空时,则赋值给 elements[tail] = e。这也证实了 ArrayDeque 是不允许值为空的。当 tail 和 head 的值相等时候,则调用 doubleCapacity() 方法。这里为何要判断是否相等呢?这是因为在 ArrayDeque 中数组是当作环形来使用的,索引0看作是紧挨着索引(length-1)之后的。
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}
这里的扩容是进行翻倍的扩容: int newCapacity = n << 1。这里还是跟 ArrayList 有所区别的。
测试:
package zzw.cn.queue;
import java.util.ArrayDeque;
/**
* @author 鹭岛猥琐男
* @create 2019/8/19 20:21
*/
public class TestDeque
{
public static void main(String[] args)
{
ArrayDeque<Integer> deque = new ArrayDeque<>();
//向尾部添加元素
deque.addLast(20);
deque.addLast(21);
deque.addLast(22);
deque.addLast(23);
//向头部添加元素
deque.addFirst(1);
deque.addFirst(2);
deque.addFirst(3);
//按照这样的添加顺序,这里的顺序应该为:3,2,1,20,21,22,23
System.out.println("队列元素为:");
for (Object o : deque)
{
System.out.print(o + ",");
}
}
}
测试结果:
队列元素为:
3,2,1,20,21,22,23,