【Java源码】集合类-ArrayDeque

一、类继承关系

ArrayDeque和LinkedList一样都实现了双端队列Deque接口,但它们内部的数据结构和使用方法却不一样。根据该类的源码注释翻译可知:

ArrayDeque实现了Deque是一个动态数组。

ArrayDeque没有容量限制,容量会在使用时按需扩展。

ArrayDeque不是线程安全的,前面一篇文章介绍Queue时提到的Java原生实现的 Stack是线程安全的,所以它的性能比Stack好。

禁止空元素。

ArrayDeque当作为栈使用时比Stack快,当作为队列使用时比LinkedList快。

public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable

所以ArrayDeque既可以作为队列(包括双端队列xxxFirst,xxxLast),也可以作为栈(pop/push/peek)使用,而且它的效率也是非常高,下面就让我们一起来读一读jdk1.8的源码。

二、类属性 //存储队列元素的数组 //power of two transient Object[] elements; //队列头部元素的索引 transient int head; //添加一个元素的索引 transient int tail; //最小的初始化容量(指定大小构造器使用) private static final int MIN_INITIAL_CAPACITY = 8;

elements是transient修饰,所以elements不能被序列化,这个和ArrayList一样。elements数组的容量总是2的幂。

MIN_INITIAL_CAPACITY是调用指定大小构造器时使用的最小的初始化容量,这个容量是8,为2的幂。

三、构造函数 //默认16个长度 public ArrayDeque() { elements = new Object[16]; } public ArrayDeque(int numElements) { allocateElements(numElements); } public ArrayDeque(Collection<? extends E> c) { allocateElements(c.size()); addAll(c); }

ArrayDeque() 无参构造函数默认新建16个长度的数组。

上面第二个指定容量的构造函数,以及第三个通过Collection的构造函数都是用了allocateElements()方法

四、ArrayDeque分配空数组

ArrayDeque通过allocateElements()方法进行扩容。下面是allocateElements()源码:

private void allocateElements(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 } elements = new Object[initialCapacity]; }

首先将最小初始化容量8赋值给initialCapacity,通过initialCapacity和传入的大小numElements进行比较。

如果传入的容量小于8,那么元素数组elements的容量就是默认值8。正好是2的三次方。

如果传入容量大于等于8,那么就或通过右移(>>>)和二进制按位或运算(|)以此使得elements内部数组的容量为2的幂。

下面通过一个实例来了解大于等于8时,这段算法内部的运行:

ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(8);

我们通过new一个8个容量的ArrayDeque,进入if判断使得initialCapacity = numElements;此时initialCapacity = 8

然后执行 initialCapacity |= (initialCapacity >>> 1); 首先括号内的initialCapacity >>> 1 右移1位得到4,此时运算式便是initialCapacity|=4,通过二进制按位或运算,例:a |= b ,相当于a=a | b 。得到initialCapacity=12

initialCapacity |= (initialCapacity >>> 2);同理为12和12右移两位结果的按位或运算,得到initialCapacity=15

initialCapacity |= (initialCapacity >>> 4); 后面的步骤initialCapacity右移4位,8位,16位都是0,initialCapacity和0的按位或运算还是自己。最终得到所有位都变成了1,所以通过 initialCapacity++;得到二进制数10000。容量为2的4次方。

为什么容量必须是2的幂呢?

下面就从主要函数中来找找答案。

五、如何扩容?

扩容是调用doubleCapacity() 方法,当head和tail值相等时,会进行扩容,扩容大小翻倍。

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 r = n - p; 计算出下面需要复制的长度

int newCapacity = n << 1; 将原来的elements长度左移1位(乘2)

通过System.arraycopy(elements, p, a, 0, r); 先将head右边的元素拷贝到新数组a开头处。

System.arraycopy(elements, 0, a, r, p);再将head左边的元素拷贝到a后面

最终 elements = a;设置head和tail

六、主要函数 add()/addLast(e)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwfzj.html