public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { 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; ...... }
以上代码来源JDK1.8,对ArrayList的解析代码来源于JDK1.8
ArrayList 的底层最重要的两个属性:Object数组elementData和size属性。
除以上两个属性外还有一些默认的属性值,我就不一一介绍,通过后面的解析,大家就能明白这些属性是用来干什么的。
由此可以看出ArrayList的底层由数组实现,ArrayList中存放的值实际都是放在elementData中。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }通过观察 ArrayList的构造函数可以知道,elementData初始化时一个空的数组。
add操作过程:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
由官方注释可以看出,ensureCapacityInternal()方法主要用来增加modCount的值。
modCount成员变量是继承于AbstractList的,这个成员变量记录着集合的修改次数,也就是每次add或者remove的时候都会加1。
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
通过代码可以分析得出calculateCapacity()方法的作用是求出当前elementData至少应该给多大。
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
ensureExplicitCapacity()方法中对modCount做加1操作。
接着判断满足条件下的elementData长度与当前elementData的长度大小。如果当前elementData长度大于minCapacity,则无需改变elementData长度,反之则需要增加elementData的长度。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
grow()方法就是为当前elementData数组增加长度。
int newCapacity = oldCapacity + (oldCapacity >> 1);
由上面这行代码可知,elementData数组长度增长量是当前长度的一半向下取整。如果长度还是不够的话,那么直接增长到满足条件下的最小长度,也就是minCapacity的值。
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
由上面代码可知,数组默认最大长度是MAX_ARRAY_SIZE的值,也就是Integer.MAX_VALUE-8。当超过这个值时就需要判断当前值是否存在int值溢出问题。int的最大值是Integer.MAX_VALUE,当对Integer.MAX_VALUE值做加一时便会出现溢出问题。当newCapacity的值大于安全值即MAX_ARRAY_SIZE时,需要做溢出检查。
此处需要注意的是,做安全校验的不是newCapacity,而是minCapacity。因为当newCapacity大于安全值时,newCapacity也便没有意义了。通过hugeCapacity()方法判断满足条件下的最小长度值也就是minCapacity值是否本身就溢出了,若溢出则抛出数组下标越界异常,若无溢出,则根据值大小返回MAX_ARRAY_SIZE或Integer.MAX_VALUE的值。
确定elementData的新长度的之后调用Arrays.copyof()方法,生成新的elementData数组,并把原有的elementData中的值复制过来。
至此我们就清楚了list新增一个值时,对底层elementData长度控制机制。
这种扩容策略有一个好处,就是在添加大量元素前,通过ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量。
回顾add()方法的源码,接下来应该是
elementData[size++] = e;
准备好elementData的大小后,执行这行代码便是顺理成章的事了。
remove操作过程:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
由上面两段代码可知,remove操作时,需要先确认移除对象下标,然后通过System.arraycopy方法将下标后的所有值往前复制一个位置,最后将elementData数组最后一个值清空。
get操作:
public E get(int index) { rangeCheck(index); return elementData(index); }
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
由上面两端代码可知,get方法的操作就两步
第一步:检查下标合法性。
第二标:根据获取下标值。
set操作:
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
由上段代码可知,set方法的操作也就是根据下标替换原来的值操作,流程相当简单。
通过上面对add方法和remove方法的分析,可以看出,ArrayList的新增和删除操作都是通过Object数组的复制来处理数组的变化,size总是记录当前数组的大小。这就解释了,ArrayList添加和删除元素的效率低,原因是数组复制过程消耗资源较多。而查找和更新元素的效率比较高,原因是数组可以由下标直接定位对象位置,获取和更新值。
以上源码来源于JDK1.8。
通过上面对add方法和remove方法的分析,可以看出,ArrayList的新增和删除操作都是通过Object数组的复制来处理数组的变化,size总是记录当前数组的大小。这就解释了,ArrayList添加和删除元素的效率低,原因是数组复制过程消耗资源较多。