ArrayList是List接口基于数组的实现。它允许包括 null 在内的所有元素。每个ArrayList实例都有一个容量,该容量代表可以存储元素的多少。每个ArrayList实例都有一个初始大小,当然你也可以通过构造方法指定初始大小。随着向ArrayList中不断添加元素,其容量也自动增长。当元素数量达到当前容量最大值时会导致数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。 但是需要注意,此方法的实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
private static final int DEFAULT_CAPACITY = 10;
/** *Object类型数组 */
transient Object[] elementData;
/** * 指定初始容量 */
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);
}
}
/** * 默认构造,初始容量为10 */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/** * 使用一个集合初始化一个ArrayList */
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
//设置指定位置元素的值(可理解为更新操作)
set(int index, E element)
//往集合中添加元素,如果容量不足则扩容
add(E e)
//往指定位置插入元素,如果容量不足则先扩容。原先index及之后的元素均往后移动一位
add(int index, E element)
//删除指定位置的元素,原先index之后的元素均往前移动一位
public E remove(int index) ;
//移除此列表中首次出现的指定元素(如果存在),此操作需要遍历数组
public boolean remove(Object o) {
仔细观察我们会发现还有两个空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这两个空数组有什么用呢?我们再把上面的两个构造方法拿来看下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
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);
}
}
我们可以看到,当使用默认构造方法时,直接将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给了elementData,当使用指定容量进行构造ArrayList时,如果initialCapacity=0,将EMPTY_ELEMENTDATA赋值给了elementData,这两种方式构造出来的ArrayList初始容量均为0,那么为什么要使用两个不同的空数组呢?想知道答案我们来看下add(E e);方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
从上面代码可以看到,在add一个元素时,才对容量进行了操作,为什么要把扩容操作放在这里呢?其实是防止你new出一个数组,但是不用,导致空间资源的浪费。回到上面的问题,我们看下ensureCapacityInternal方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
看下这里有个if判断,当elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,对容量有一个赋值操作,minCapacity为DEFAULT_CAPACITY与minCapacity的最大值。显然当第一次add元素时minCapacity==1所以初始容量就为DEFAULT_CAPACITY也就是10了。所以使用两个不同的空数组的原因就是为了保证:当我们使用默认构造方法对ArrayList进行构造时,初始容量为10。
下面再来看下ensureExplicitCapacity和grow方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//当最小所需容量大于当前数组容量时进行grow操作
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//先尝试新容量为旧熔炼个的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
可以看出,当最小所需容量大于当前数组容量时进行grow操作。先尝试新容量为旧熔炼个的1.5倍,如果还不够,那么就使用minCapacity作为当前扩充的容量大小。
此外有没有注意到ensureExplicitCapacity方法中有一个modCount++的操作,这个modCount是干什么用的呢?
ArrayList的modCount是从类 java.util.AbstractList 继承的字段:
protected transient int modCount
这个字段代表已从结构上修改此列表的次数。从结构上修改是指更改列表的大小(也就是元素个数发生了变化),或者打乱列表,从而使正在进行的迭代产生错误的结果。
此字段由 iterator 和 listIterator 方法返回的迭代器和列表迭代器实现使用。如果意外更改了此字段中的值,则迭代器(或列表迭代器)将抛出 ConcurrentModificationException 来响应 next、remove、previous、set 或 add 操作。在迭代期间面临并发修改时,它提供了快速失败 行为,而不是非确定性行为。
大家可以测试下下面这段程序:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 10)
list.remove(integer); //注意这个地方,此处换成list.add(integer)结果是一样的
}
}
上面这段代码会抛出如下异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at Main.main(Main.java:12)
为何会报这个错?看下ArrayList的Iterator源码(有部分代码未贴出):
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我们可以看到当做next()、remove()操作时,都要先checkForComodification()而这个方法做的事情就是判断modCount 与expectedModCount是否相等。不相等就报错。