ArrayDeque 动态循环数组的指针和扩容代码
ArrayDeque是jdk1.6中添加的一个集合处理类。继承AbstractCollection,实现Deque双端队列接口。
主要用于实现栈和队列。
ArrayDeque底层是一个动态循环数组。
主要的实例变量为head,tail,和元素的保存位置,一个Object类型的数组。
初始化的时候 head = tail = 0 这个时候,首位指针都指向数组中的0位。通过移动head/tail的位置来处理元素的位置。
ArrayDeque默认长度为16(当使用其他集合初始化的时候,底层元素长度为传入的集合的长度+1)
底层数组没有元素的位置均为空且数组尾部最少有一个空
位置处理方法:inc方法用于将head/tail向后移动,dec方法用于将head/tail向前移动。后面的三参数inc和sub是这两个方法的变形,将移动的距离从固定的1变成参数传递。
static final int inc(int i, int modulus) {
if (++i >= modulus) i = 0;
return i;
}
static final int dec(int i, int modulus) {
if (--i < 0) i = modulus - 1;
return i;
}
static final int inc(int i, int distance, int modulus) {
if ((i += distance) - modulus >= 0) i -= modulus;
return i;
}
static final int sub(int i, int j, int modulus) {
if ((i -= j) < 0) i += modulus;
return i;
}
ArrayDeque中处理元素的方法主要通过移动头尾指针来处理。在增加元素的时候,分别移动头/尾方法对应的指针,头指针前一,尾指针后移,并将添加的元素放置在移动后指针所在的位置上。在移除元素的时候,头指针后移,尾指针前移,并将原来位置上的元素设置为null;添加的元素是不能为空的,或者会触发NullPointerException。
用作栈和队列的时候,栈处理的方法就是从头部添加,头部拿出,队列的处理方法就是头部插入,尾部拿出。
//从头部添加
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
final Object[] es = elements;
es[head = dec(head, es.length)] = e;
if (head == tail)
grow(1);
}
//从尾部添加
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
final Object[] es = elements;
es[tail] = e;
if (head == (tail = inc(tail, es.length)))
grow(1);
}
//从头部移除
public E pollFirst() {
final Object[] es;
final int h;
E e = elementAt(es = elements, h = head);
if (e != null) {
es[h] = null;
head = inc(h, es.length);
}
return e;
}
//从尾部移除
public E pollLast() {
final Object[] es;
final int t;
E e = elementAt(es = elements, t = dec(tail, es.length));
if (e != null)
es[tail = t] = null;
return e;
}
扩容方法:触发时机,在放入元素之后头尾指针相遇,或者addAll方法之后现有的数组中的空余位置不足以容纳新增的元素。扩容新增的容量结果分别为原来的容量+2(原来的容量<64)或者原来的容量*1.5(原有容量大于等于64) 如果这样比需要新增的容量小,则直接新增传入的容量。容量最大为Integer.MAX_VALUE-8;
private void grow(int needed) {
//旧容量
final int oldCapacity = elements.length;
//新容量
int newCapacity;
//如果原有体积较小则容量翻倍 或者就增加50% 比较的标准是旧容量是否小于64 这里的jump代表增加的容量
int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
//如果上一次计算得到的容量低于所需的容量 则对增加的容量再次进行计算
if (jump < needed
|| (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0){
//对新的容量进行溢出计算
newCapacity = newCapacity(needed, jump);
}
//数组扩容
final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
if (tail < head || (tail == head && es[head] != null)) {
// 这里代表头尾指针相遇 这个时候需要将头指针向后移动新增加的容量长度
int newSpace = newCapacity - oldCapacity;
//复制旧的数组给新的数组
System.arraycopy(es, head,es, head + newSpace,oldCapacity - head);
//将新增容量的地方全部赋值null
for (int i = head, to = (head += newSpace); i < to; i++)
es[i] = null;
}
}
LinkedList和ArrayDeque的区别
LinkedList底层是双向链表结构,ArrayDeque底层是动态循环数组
做队列的时候ArrayDeque效率快于LinkedList
ArrayDeque的操作(插入、删除、访问)效率为均摊的O(1)
LinkedList的操作(插入、删除)效率为O(1) 访问为O(n) 这是链表实现造成的效率
ArrayDeque不可操作null,LinkedList可以操作空
LinkedList因为需要在元素本身之外存储双向链表的指针 所以大部分情况下占用的内容都要大于ArrayDeque