概要
最近持续阅读Java集合源码实现,学习,写文章,分析,画图,感觉写技术文章是个很有难度的活。既要自己弄懂,也要想尽办法,敲打任何文字或制作图片来简单的描述各种复杂的原理,为的是让读者更容易的了解每个实现的原理。这些就需要花费笔者很多时间,白天默默的在公司劳作,晚上还要抽出时间学习,并写成文章,真心觉得累。没办法,程序猿就得学的比别人更勤奋,更努力才行,否则未来就得被新人淘汰出局。技术的路上就需要多学习,多总结,多写文章分享,慢慢提升。
好了,说了一些感悟,进入正题吧。
在java.util包中有一个用于双端队列的集合类ArrayDeque,也是包中唯一的队列实现类,这个类大家平常用的比较少,我们还是来先看看它的源码实现,深入分析,一探究竟,为java.util.concurrent包中的BlockingQueue深入分析作准备。
来看下源码中关于ArrayDeque的英文说明如下:
Resizable-array implementation of the Deque interface. Array
deques have no capacity restrictions; they grow as necessary to support
usage. They are not thread-safe; in the absence of external
synchronization, they do not support concurrent access by multiple threads.
Null elements are prohibited. This class is likely to be faster than
Stack when used as a stack, and faster than LinkedList
when used as a queue.
解读关键点:
1、ArrayDeque是基于数组实现的双端队列,可以在头尾插入或取出元素,队列的容量无限制,满的时候会扩容。从这点可以看出ArrayDeque为非阻塞队列。
2、非线程安全,如果有多线程访问,建议在外部进行同步。
3、不允许null元素插入。
4、如果ArrayDeque用作栈,它会比Stack类要快,如果用作队列,它会比LinkedList快。
数据结构
来看下如下代码的数据存储结构:
ArrayDeque<String> queue = new ArrayDeque<>();
queue.add("语文");
queue.add("英语");
queue.add("数学");
存储结构图如下:
ArrayDeque数据存储结构ArrayDeque采用一个数组,并维护了head和tail两个索引数代表队列头和队列尾。来看下支持数据结构的属性及构造方法:
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;
// 默认创建的队列容量为16
public ArrayDeque() {
elements = new Object[16];
}
// 创建指定容量的队列,如果这个数小于最小容量,则初始化容量为8,
// 如果大于最小容量,则采用逻辑右移的操作,
// 初始化为靠近所给容量值的2的幂次方大小
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
}
从源码中得出,ArrayDeque维护了一个Object的数组,允许存放任意实例对象,且队列的容量只能为2的幂次方的大小,默认初始容量为16。
基本操作
1、取元素操作
声明:下面介绍的操作只针对队列头部,队列尾部的操作是对称的,理解其一即可。
取元素操作方法有:
1、peak(),内部调用peekFirst():只取头元素,不删除。
2、poll(),内部调用pollFirst():取头元素的同时,将元素删除(设为null),如果队列没有任何元素,返回null。
3、pop(),内部调用removeFirst():取头元素的同时,将元素删除。如果队列空,则抛出NoSuchElementException异常。
4、element(),内部调用getFirst():只取头元素,不删除。如果队列空,则抛出NoSuchElementException异常。
一般调用比较多的方法是poll方法,因为在队列为空时不会抛出异常,只返回null。来看下源码实现,比较简单,就不详述了:
public E peekFirst() {
// elements[head] is null if deque empty
return (E) elements[head];
}
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
public E removeFirst() {
E x = pollFirst();
if (x == null)
throw new NoSuchElementException();
return x;
}
public E getFirst() {
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
2、插入元素操作
声明:下面介绍的操作只针对队列尾部插入,队列头部的操作是对称的,理解其一即可。
插入元素的操作方法有:
1、add(),内部调用addLast():在队列尾插入元素,不允许插入null元素,否则抛出NullPointerException异常。如果超过容量,则扩容到原来2倍。
2、offer(),内部最终调用addLast():与add方法一致。
3、push(),内部调用addFirst():在队列头插入元素,不允许插入null元素,否则抛出NullPointerException异常。如果超过容量,则扩容到原来2倍。
3、扩容实现
扩容方法实现如下:
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;
}
新容量为原来的2倍(n << 1就是乘2的动作)。然后采用本地方法System.arraycopy()分两次将全部元素复制到新数组中。
扩容的条件是:队列尾索引追上了队列头索引,也就是两个索引值相等,表示容量已经耗完。
网友评论