public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
实现了三个接口。
- RandomAccess即随机访问,这个接口没有实际意义,可以理解为实现了这个接口标志着该类具有随机访问的功能。在下面的分析中可以看到ArrayList的底层是数组,确实具有随机访问的功能。
- Cloneable,实现了这个接口必须实现clone方法,其实Object类也具有clone方法,然鹅是不能执行的,会抛出CloneNotSupportedException(除非实现了Cloneable接口)。同时注意Object中的clone方法是浅拷贝。关于深拷贝和浅拷贝不在这里细说,详情可以移步另一篇博客。
- 序列化接口
private static final int DEFAULT_CAPACITY = 10;
// 空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空数组中添加第一个元素后扩容至DEFAULT_CAPACITY
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 设置为transient是因为避免使用java默认的序列化在数组并不满的情况下把数组全部序列化,
// 使用write/readObject
transient Object[] elementData;
初始化
ArrayList的构造函数初始化主要有根据容量和Collection及其子类来进行初始化的方式以及无参初始化三种方式。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
首先进行数组转换,然后将数组转为Object数组赋给elementData
扩容
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);
}
可以看到,添加一个元素所要求的最小容量是size+1,我们姑且称之为目标容量。
如果目标容量比现有的数组容量大,就需要扩容操作。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
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);
}
扩容的过程:
新容量是旧容量的1.5倍
如果新容量<目标容量,新容量=目标容量
如果新容量>MAX_ARRAY_SIZE,此时的新容量太大,调用 hugeCapacity进行调整新容量
最后完成数组的复制
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果目标容量>MAX_ARRAY_SIZE,新容量= Integer.MAX_VALUE ;否则,新容量= MAX_ARRAY_VALUE
删除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
删除的话,当然不需要管扩容的问题,但是删掉中间某个位置的元素势必会引起后边元素挪动,这也是数组的特点。
同时可以看到这里的删除的实现实际上是将某处的对象置为null,即变为弱引用,让GC来清理。具体的引用问题请移步另一篇博客
同时注意添加删除都会导致有一个modCount变量的变化,该变量在其父类AbstractList
protected transient int modCount = 0;
这个主要是用在迭代遍历的时候,引出了ArrayList的一个内部类
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification();
// operate something
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
可以看到modCount在这里发挥作用,迭代遍历元素时,都会比较当前modCount和expectedModCount是否相等,不相等就会抛出ConcurrentModificationException,显然,我们上便看到了,对list进行更改操作就会modCount++,使得其与expectedModCount不相等,很明显,这是ArrayList为了线程安全而设置的一个小trick,所谓的快速失败机制
总结
因为底层是数组,所以ArrayList的操作都很简单,同样需要注意到数组中元素除了查找很快,别的操作都可能需要大量元素的移动,代价较大。同时通过modCount实现了快速失败机制。
关注公众号:java蹲坑读物
跟我蹲坑学习,不错过每一分钟