ArrayList分析
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 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 = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList里常用6个属性:
四个常量属性:
serialVersionUID:是用于在序列化和反序列化过程中进行核验的一个版本号。
DEFAULT_CAPACITY:默认的初始化容量10
EMPTY_ELEMENTDATA:空实例时的数组实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认大小的空实例时的数组实例
前两个解释的比较抽象
elementData:Object数组,集合真正用来存数据的容器。
size:集合大小
关于serialVersionUID可以看一下这篇博客。
serialVersionUID 是干什么的? - 知乎 (zhihu.com)
ArrayList的三个构造器(构造方法):
public ArrayList() //空参
public ArrayList(int initialCapacity) //带有初始化容量
public ArrayList(Collection<? extends E> c)//以一个集合来初始化
因为ArrayList集合底层就是采用elementData[]数组来存储数据,在创建集合时:
1、使用空参构造器时。
只是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋值给了elementData数组来完成集合的初始化,因为DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前创建好的,所以在创建集合时的操作只是一个赋值操作,简化了创建数组的时间。
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2、使用有参(initialCapacity)构造方法时。不废话,先看源码:
他会首先判断传进来的initialCapacity是否是大于0的:
如果不大于0他执行的操作和使用空参构造器执行的操作类似,只是这次赋值给elementData数组的是EMPTY_ELEMENTDATA数组,此次空数组赋值竟然和上次空参不一样,这是为什么呢?为什么要创建出两个空数组呢?暂且按下不表。
如果大于0,他会新创建一个initialCapacity大小的数组赋值给elementData。至于传进来的是一个小于0的数,当然结果就是死路一条。
3、构造方法参数是集合时。
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
他会首先将传进来的集合转化为一个Object数组,然后判断该数组是否为空。
如果为空,他仍会执行给elementData数组赋值EMPTY_ELEMENTDATA数组的操作,到这有没有发现奇妙的一点?无论集合第一次创建时采用的哪一个构造方法,如果传进来的是一个“空内容”,他都会使用底层已经创建好的数组来完成初始化。
如果不为空,判断他们两个是否是同一个集合。如果是,将a数组直接赋值给elementData。如果不是,将a数组复制到elementData。
问题:
为什么ArrayList源码里要定义两个空数组呢?直接定义一个岂不是更节省空间?
可以看一下源码中对DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的注释:
We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
大致意思就是:是为了在第一次添加元素时判断去给数组inflate(扩容)多少。 这就涉及到了集合在第一次添加元素时的操作和集合的扩容。
以下时第一次添加时的源码。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
在第一次添加元素时会先调用ensureCapacityInternal方法,同时将添加一个元素后的集合大小size传参过去,在ensureCapacityInternal方法中调用ensureExplicitCapacity()方法,在执行此方法之前,会先调用calculateCapacity方法。如果此时elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,也就是采用的空参构造器的初始化的第一次添加,则返回默认容量和当前容量的较大者,当然第一次添加肯定是返回默认容量(DEFAULT_CAPACITY)。最后进入ensureExplicitCapacity方法,根据minCapacity - elementData.length > 0 判断容量是否足够,然而判断是否要执行扩容方法grow,;
走到这就会发现,ArrayList的初始化容量不会在创建集合时进行,而会在第一次添加元素时进行。而且只有采用的是空参构造方法时,才会在第一次添加元素时将最小容量设置为minCapacity = DEFAULT_CAPACITY = 10。
这还不是最终的扩容,最终的扩容在grow方法中:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//让newCapacity等于原来容量的1.5倍
if (newCapacity - minCapacity < 0) //如果newCapacity比minCapacity小
newCapacity = minCapacity; //让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);
}
建议
因为ArrayList每扩容一次都要将原数组数据复制到新数组,所以建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略。