主要特点
- ArrayDeque是通过可变数组的方式实现了Deque接口。
- ArrayDeque没有容量限制,为了满足需求会自动容量会自动增长。
- ArrayDeque不是线程安全的。
- 不允许存放null。
- 当作stack使用时,效率比java.util.Stack快;当作queue使用时,效率比java.util.LinkedList快。
- 大部分的操作都是常数时间运行
JDK实现的关键点
将数组当作环来使用
在ArrayDeque中,将数组当成环形来使用的。head 指向首端第一个有效元素, tail 指向尾端第一个可以插入元素的空位,因为是循环数组,所以 head 不一定总等于0, tail 也不一定总是比head 大。
通过掩码运算避免数组越界
通过calculateSize方法确保,数组的长度为2的倍数,这样就可以通过掩码运算来避免判断数组越界。
如何当前是数组的大小16,那么最大的索引值就是(length - 1)就是15, 如果此时(tail + 1) 正好超出索引值也就是最后位置增1 = 16,但是通过掩码运算(tail = (1000 & 0111))得到0
如果当前是数组的大小16,那么最大的索引值就是(length - 1)就是15,如果此时(head - 1) 正好超出索引值也就是第一位减1 = -1,但是通过掩码运算(tail = (1111 & 0111))会得到15
当head跟tail相等时,对数组进行扩容
// ****** Array allocation and resizing utilities ******
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
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
}
return initialCapacity;
}
数组扩容操作
如果tail值与head相等则翻倍扩展,在扩容时需要进行数组拷贝
/**
* Doubles the capacity of this deque. Call only when full, i.e.,
* when head and tail have wrapped around to become equal.
*/
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;
}
在尾部添加元素
add(e) addLast(e) offer(e) offerLast(e),这几个方法最终都调用了addLast(e)
先将elements[tail]设置为需要插入的元素,插入之后将tail+1,并判断是否可以扩容数组,这里其实牺牲了一个数组的位置。
/**
* Inserts the specified element at the end of this deque.
*
* <p>This method is equivalent to {@link #add}.
*
* @param e the element to add
* @throws NullPointerException if the specified element is null
*/
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
在头部添加元素
offerFirst(E e) addFirst(E e)
/**
* Inserts the specified element at the front of this deque.
*
* @param e the element to add
* @throws NullPointerException if the specified element is null
*/
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
从头部获取(删除)元素
方法element()、peek()、peekFirst()、getFirst()不需要从数组中删除元素,只需要根据head从数组中获取(elements[head])即可。
方法poll()、pollFirst()、pop()、removeFirst()获取元素之后,要将元素删除,需要将数组索引head中值设置为null,以便虚拟机的垃圾回收器回收,并重新设置head的值。这几个方法最终都是调用了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;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
从尾部获取(删除)元素
方法getLast()、peekLast()获取尾部元素,因为tail值是数组中最后一位的索引加一之后的结果,所以在获取时要减去一
/**
* @throws NoSuchElementException {@inheritDoc}
*/
public E getLast() {
@SuppressWarnings("unchecked")
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
@SuppressWarnings("unchecked")
public E peekLast() {
return (E) elements[(tail - 1) & (elements.length - 1)];
}
方法pollLast()以及removeLast()获取并删除尾部元素,所以在获取到元素之后,需要将数组中对应索引位置的元素设置为null,以便虚拟机垃圾回收器进行回收。removeLast()方法里面调用了pollLast(),所以这里只展示pollLast()的代码:
public E pollLast() {
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t;
return result;
}
是否包含元素
该方法从head开始,利用掩码运算,遍历整个数组,判断某个元素是否在数组中
/**
* Returns {@code true} if this deque contains the specified element.
* More formally, returns {@code true} if and only if this deque contains
* at least one element {@code e} such that {@code o.equals(e)}.
*
* @param o object to be checked for containment in this deque
* @return {@code true} if this deque contains the specified element
*/
public boolean contains(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
i = (i + 1) & mask;
}
return false;
}