关于队列这个数据结构,大家应该都是比较熟悉。列队是一种先进先出(FIFO)的数据结构。删除操作只能在表的头部,插入操作只能在表的尾部。
Queue一般是作为一个缓冲队列使用的,简单举例:生产端的生产速度偶尔会大于消费端的消费速度,但又不想等待对方,此时如果有个缓存队列这样的问题就解决啦。生产端生产的东西只管放入缓存队列,消费端从缓存队列里面拿东西,达到一个缓冲的目的。
LinkedList是基于链表来实现,ArrayQueue基于数组实现。下面我们会主要讲一下这ArrayQueue者的源码实现。
ArrayQueue
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
//实际存放元素的数组
transient Object[] elements;
//头指针
transient int head;
//尾指针
transient int tail;
//默认最小容量
private static final int MIN_INITIAL_CAPACITY = 8;
}
ArrayQueue初始化
//未制定容量时,默认为16
public ArrayDeque() {
elements = new Object[16];
}
//制定初始容量
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
//指定初始值
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
//指定容量小于8时,返回8
//否则,返回initialCapacity=2^n,2^n越界时,则取2^30
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
elements = new Object[initialCapacity];
}
ArrayQueue-offer()
//添加一个元素并返回true,如果队列已满,则返回false
public boolean offer(E e) {
return offerLast(e);
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
//添加一个元素置尾端
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
//添加元素置尾端
elements[tail] = e;
//尾指针tail+1,计算tail在elements中的位置
//例如:数组长度8,tail+1=8,则tail=0
//当tail==head时,即数组容量不足,进行扩容操作
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
//双倍扩容
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
//新容量=当前容量*2
int newCapacity = n << 1;
//新容量过大,抛错
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
//创建一个新容量大小的数组
Object[] a = new Object[newCapacity];
//将elements拷贝置a中
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
//注意:因为满了才进行扩容,所以tail=n
//如果不是满了才扩容,这里会出现问题
tail = n;
}
ArrayQueue-poll()
//移除并返回第一个元素,如果队列为空,返回null
public E poll() {
return pollFirst();
}
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
//取到后‘必须’把当前位置设为null
elements[h] = null;
head = (h + 1) & (elements.length - 1);
return result;
}
ArrayQueue-peek()
//返回头部元素,如果队列为空,返回null
public E peek() {
return peekFirst();
}
public E peekFirst() {
// 如果head位置没有值,则返回null
return (E) elements[head];
}
//返回头部元素,如果为null,抛错
public E element() {
return getFirst();
}
public E getFirst() {
//获取头部元素
@SuppressWarnings("unchecked")
E result = (E) elements[head];
//如果为null,抛错
if (result == null)
throw new NoSuchElementException();
return result;
}
ArrayQueue-remove(e)
//移除头段元素并返回,如果为null,则抛错
//对比poll()方法,多了一个判空抛错的动作
public E remove() {
return removeFirst();
}
public E removeFirst() {
E x = pollFirst();
if (x == null)
throw new NoSuchElementException();
return x;
}
ArrayQueu-add(e)
//增加一个元素至尾端(队列满,则扩容)
public boolean add(E e) {
addLast(e);
return true;
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
//tail后移一位
//检测是否已满,扩容
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
ArrayQueu-push(e)
//增加一个元素至首端(队列满,则扩容)
public void push(E e) {
addFirst(e);
}
public void addFirst(E e) {
//e不能为空
if (e == null)
throw new NullPointerException();
//heah前移一位,并设置当前位置的值
elements[head = (head - 1) & (elements.length - 1)] = e;
//如果队列满,则扩容
if (head == tail)
doubleCapacity();
}
ArrayQueue-spliterator()
关于ArrayQueue的并行遍历器是怎么实现的
public Spliterator<E> spliterator() {
return new DeqSpliterator<E>(this, -1, -1);
}
static final class DeqSpliterator<E> implements Spliterator<E> {
private final ArrayDeque<E> deq;
//围栏值,index能到达的最大位置
private int fence; // -1 表示第一次使用
//当前位置指针
private int index; // current index, modified on traverse/split
DeqSpliterator(ArrayDeque<E> deq, int origin, int fence) {
this.deq = deq;
this.index = origin;
this.fence = fence;
}
//获取围栏值值
private int getFence() { // force initialization
int t;
if ((t = fence) < 0) {
//第一次初始化时,获取index和fence的值
t = fence = deq.tail;
index = deq.head;
}
return t;
}
//分割,因为head可能比tail大,因此中间位置需要计算一下
public DeqSpliterator<E> trySplit() {
int t = getFence(), h = index, n = deq.elements.length;
//h == t 表示没有数据
//(h + 1) & (n - 1)) == t 表示只有一个数据
if (h != t && ((h + 1) & (n - 1)) != t) {
//h > t 表示head>tail时
if (h > t)
t += n;
//计算m的位置
//例如:n:8,head:6,tail:4
// 则 t=4+8=12
// m =((6+12)>>>1)&(8-1)=1
int m = ((h + t) >>> 1) & (n - 1);
return new DeqSpliterator<>(deq, h, index = m);
}
return null;
}
public void forEachRemaining(Consumer<? super E> consumer) {
if (consumer == null)
throw new NullPointerException();
Object[] a = deq.elements;
int m = a.length - 1, f = getFence(), i = index;
index = f;
while (i != f) {
@SuppressWarnings("unchecked") E e = (E)a[i];
i = (i + 1) & m;
//遍历时,发生‘结构变更’,e可能为null
if (e == null)
throw new ConcurrentModificationException();
consumer.accept(e);
}
}
...
//特征值
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED |
Spliterator.NONNULL | Spliterator.SUBSIZED;
}
}
}
可以看到ArrayQueue的并行迭代器没有什么特别的,只不过我们以前看的ArrayList,HashMap等数据结构都是通过一个modCount来保证快速失败的,而ArrayQueue则是在遍历时,但它结构发生变更,并不一定会抛错!
ArrayQueue 小结
可以看到,ArrayQueue的内部是基于一个数组来实现,通过两个指针head和tail来分别指向头端和尾端,当head和tail相等时,说明容量已满,此时会进行扩容操作。
另外,可以思考一下ArrayQueue的使用场景!