(ps:如果不想看分析,可以直接跳到末尾看扩容结论)
①ArrayList 底层就是使用数组实现的,从名字可以瞅出来对吧,再者它是支持泛型的,所以它的内部使用的是 Object 类型的数组,名为 elementData,这样可以兼容各种数据类型;
transient Object[] elementData;
②来看几个与 elementData 数组相关的内部属性:
//一个空数组:elementData 默认初始化会用到这家伙
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;//默认容量值(jdk8)
private int size;//用于表示 elementData 数组中被使用的大小
③再看下 ArrayList 的初始化容量设置:
//1.根据上面的属性介绍可以看出,不指定容量的 ArrayList 创建出来,只是一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
//如果指定了容量,那么 elementData 的大小就会是指定的大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//2.如果指定的容量为 0,elementData 同样会被赋值为一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
总结:从上面的代码和注释可以总结出,在不指定容量大小或者容量指定为 0 的这两种情况下,ArrayList 中存储数据的数组 elementData 都会被赋值为一个空数组,容量不是默认容量,而是 0;
ok,准备工作完毕,下面开始由一个 add(E e) 添加数据的方法,引出 ArrayList 的扩容机制,从 add(E e) 源码开始吧:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
在 add() 方法中调用了 ensureCapacityInternal() 方法,我们需要注意的就是调用这个方法时传入的参数,它是 size+1 ,前面已经提到了 size ,它是 elementData 中存在的有效数据个数,之所以传递 size+1 ,是因为它是调用 add() 方法后,数据的总数,也是 elementData 数组必须要达到的最小容量,进入 ensureCapacityInternal() 方法中瞅瞅:
//留意:minCapacity 的含义就是 elementData 的大小必须要大于等于它,
// 不然就要扩容,后续介绍的方法中,此次名称的参数含义也是如此
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 作用:此方法的作用就是根据 elementData 和 minCapacity 再次确定
// 下 minCapacity 的大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断 elementData 是否为 [],如果为空,也能说明 add() 方法也是
//第一次被调用,那么 minCapacity 的值就是 1,最后的返回值就是
//默认容量值 10,这就是默认容量值作用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果 elementData 不是 [],就直接返回原本dd minCapacity
return minCapacity;
}
上面的分析中,主要就是对 minCapacity 进行了一次重新的计算,前面也提到了它的意义,ArrayList 中的默认容量值也是在这里被使用到的,好了,对 minCapacity 进行了重新计算后,会将它传入 ensureExplicitCapacity() 方法中,继续往下看:
//建议:你瞧,这个参数也叫 minCapacity ,所以看这几个方法的时候,
// 可以直接将 minCapacity 看作是用在这几个方法直接的全局变量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
上面的方法中判断了以 elementData 现在的容量是否能够容纳的下 minCapacity 个数据,如果能的话,扩容个锤子,直接返回到了 add() 方法中将数据添加到现在的 elementData 数组中了,如果不能,才会进入 grow() 方法,这个方法就是真正的扩容方法:
private void grow(int minCapacity) {
//1.首先取出 elementData 数组的长度
int oldCapacity = elementData.length;
//2.以位运算的方式计算得到 elementData 容量的 1.5 倍
//重点:这里就是 ArrayList 普遍扩容时的容量变化
int newCapacity = oldCapacity + (oldCapacity >> 1);
//3.下述重点分析
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//补充:这里是为了应对当 elementData 很大的时候,扩容引起的内存变化
// 也会非常大,容易导致 OOM 出现而设置的
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//到这里,新数组的容量就已经定好了,使用工具方法进行了扩容赋值操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
==重点分析(3):==这里的判断是应对特殊情况的,因为一般情况下,newCapacity 是不会小于 minCapacity 的,这里针对的是两种情况,一种是 elementData 被赋予的是空数组,那么此时的 newCapacity 为 0,而 minCapacity 为 10,很显然,需要使用 10 来进行扩容,另外一种情况是一个细节,就是创建 ArrayList 的时候,指定的容量是 1,那么此时的 newCapacity 虽然是经过 oldCapacity 运算过的,但还是 1,而 minCapacity 是 2,所以会使用 minCapacity 进行扩容;
总结:从以上的分析中,我们可以了解到的就是,不指定容量的情况下创建出来的 ArrayList 是一个 [] 空数组,当这种 ArrayList 被添加数据时,会直接将内部数组的大小扩容至默认的容量大小 10(jdk8),如果指定容量大小为 1,扩容之后会是 2,其他的普遍扩容,elementData 的容量都是原来的 1.5 倍大小,对于一些比较大的 ArrayList ,在扩容的时候,也进行了适当的限制,减小了由于数组过大而引发 OOM 出现的概率;
(ps:关于 ArrayList 的最大容量,还没理解设计者的含义,欢迎讨论)