小豹子带你看源码:ArrayList

世界上最牛的 Java 代码去哪找?固然是 JDK 咯~计划学习一下常见容器的源码。 我会把我以为比较有意思或者玄学的地方更新到这里。java

如下 JDK 源码及 Javadoc 均从 java version "1.8.0_131" 版本实现中摘录或翻译 java.util.ArrayList算法


首先,开头就颇有意思,声明了两个空数组:编程

116 行 - 126 行数组

/** * Shared empty array instance used for empty instances. */
private static final Object[] EMPTY_ELEMENTDATA = {};

/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
复制代码

有什么差异?注释上告诉咱们,EMPTY_ELEMENTDATA 用于空数组实例,而 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 用于默认的空数组实例,他们的区别在因而否能获知首次添加元素时对数组的扩充量。这样看咱们也是一头雾水,不如咱们看一下他是怎样应用的:bash

143行-166行多线程

/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/** * Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

咱们看,当咱们使用 new ArrayList() 建立 ArrayList 实例时,elementData 被赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而若是咱们这样建立实例 new ArrayList(0)elementData 将会被赋值为 EMPTY_ELEMENTDATA。这看似没什么区别,咱们再看一下扩容数组的函数。并发

222 行 - 228 行ide

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
复制代码

这个函数中对 elementData 的引用进行判断,若是引用是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 则会在 DEFAULT_CAPACITY(值为10)minCapacity 中选择最大值为扩容后的长度。这里至关于用 elementData 的引用值作一个标记:若是咱们使用 new ArrayList() 建立实例,则 ArrayList 在首次添加元素时会将数组扩容至 DEFAULT_CAPACITY 若是咱们使用 new ArrayList(0) 建立实例则会按照 newCapacity = oldCapacity + (oldCapacity >> 1); (255行) 规则扩充实例。
那么,为何这样作。咱们想,咱们使用空构造器和 new ArrayList(0) 建立实例的应用场景是不同的,前者是咱们没法预估列表中将会有多少元素,后者是咱们预估元素个数会不多。所以ArrayList对此作了区别,使用不一样的扩容算法。
然而令我惊讶的是,经过引用判断来区别用户行为的这种方式,这是我想不到的,若是是我,我必定会再设置一个标志变量。函数


238 行 - 244 行高并发

/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
复制代码

这个变量显然是数组的最大长度,可是为何要 Integer.MAX_VALUE - 8 呢,注释给了咱们答案:一些 VM 会在数组头部储存头数据,试图尝试建立一个比 Integer.MAX_VALUE - 8 大的数组可能会产生 OOM 异常。


246 行 - 262 行

/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码

此处是扩容数组的核心代码,其计算新数组的长度采用 oldCapacity + (oldCapacity >> 1),新数组大概是原数组长度的 1.5 倍,使用位运算计算速度会比较快。
然而我想说的并非这个。而是这句话 if (newCapacity - minCapacity < 0)。在 ArrayList 类代码中,随处可见相似 a-b<0 的判断,那么为何不用 a<b 作判断呢?
下面是我写的测试代码:

int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
int newCap = 65421;
System.out.println(newCap > MAX_ARRAY_SIZE);
System.out.println(newCap - MAX_ARRAY_SIZE > 0);
复制代码

这段代码的输出是 false false,咱们能够看到,两种写法在正常状况下是等效的。可是咱们考虑一下若是 newCap 溢出呢?咱们令 newCap = Integer.MAX_VALUE + 654321 输出结果就变成 了false true。 这是为何?

newCap:                  -2147418228
                          10000000000000001111111110001100
MAX_ARRAY_SIZE:          2147483639
                          01111111111111111111111111110111
MAX_ARRAY_SIZE*-1:       -2147483639
                          10000000000000000000000000001001
newCap - MAX_ARRAY_SIZE: 65429
                          00000000000000001111111110010101
复制代码

咱们看,newCap 因为溢出,进位覆盖了符号位,所以 newCap 为负,咱们将 newCap - MAX_ARRAY_SIZE 当作 newCap + MAX_ARRAY_SIZE*-1,加和时两符号位相加又变成了 0(两个大负数相加又一次溢出),结果为正,而此次溢出偏偏是咱们想要的,获得了正确的结果。


649 行 - 675 行

/** * The number of times this list has been <i>structurally modified</i>. * Structural modifications are those that change the size of the * list, or otherwise perturb it in such a fashion that iterations in * progress may yield incorrect results. * * <p>This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * <i>fail-fast</i> behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * * <p><b>Use of this field by subclasses is optional.</b> If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */
protected transient int modCount = 0;
复制代码

这个变量颇有意思,它是用来标记数组结构的改变。每一次数组发生结构改变(好比说增与删)这个变量都会自增。当 List 进行遍历的时候,遍历的先后会检查这个遍历是否被改变,若是有改变则将抛出异常。 这种设计体现了 fail-fast 思想,这是一种编程哲学。尽量的抛出异常而不是武断的处理一个可能会形成问题的异常。这种思想在不少应用场景的开发(尤为是多线程高并发)都起着重要的指导做用。ErLang 就很提倡这种作法。

后记

看完 ArrayList 的源码,我内心有这样的感觉:严谨,高效,还有不少我以前不知道的操做。本身的代码和大牛的代码差距仍是很大的。看完这些我也不知道我能吸取多少……慢慢来吧。

相关文章
相关标签/搜索