最近也是回去看了看ArrayList相关的源码,觉得受益匪浅。还是写篇博客记录一下。
从源码的开头头看,ArrayList定义了几个属性。
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
我们可以看到其中有一个私有属性DEFAULT_CAPACITY,表示数组的初始容量是10。两个空数组EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA,至于有什么区别,下面会讲到。elementData数组是真正存放数据的数组。size属性是记录elementData数组的长度。
那EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个数组有什么区别呢?
我们先看看ArrayList的有参和无参的构造方法。
//有参构造
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);
}
}
//无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
首先我们看有参构造,传入一个initialCapacity参数,表示定义的ArrayList的容量大小。如果initialCapacity大于0,直接实例化一个initialCapacity大小的object数组。如果initialCapacity等于0,那就把elementData数组实例化为EMPTY_ELEMENTDATA空数组。
然后我们看一下无参构造,直接就是把elementData数组实例化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组。
所以EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个空数组的区别就是:一个是有参构造是参数为0的时候实例化的,一个是无参构造时实例化的。
下面我们看一下ArrayList的常用方法的底层实现。
add方法
下面我们具体看一下add方法的具体实现。
我们可以看到add方法内部调用了ensureCapacityInternal方法,使用ensureCapacityInternal方法对判断数组是否要进行扩容,要的话就扩容原数组。然后给原来数组的最后一个元素后面的一个元素赋值。
下面我们具体看一下ensureCapacityInternal方法的具体实现。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal方法中先判断你现在elementData数组是否为空数组,如果时空数组,那就看看传入的size + 1是否比数组初始容量大。选择较大的一方作为数组容量。然后内部又调用了ensureExplicitCapacity方法。
下面我们具体看一下ensureExplicitCapacity方法的具体实现。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount保存的是ArrayList结构的修改次数,一般当add、remove时会增加。在下面迭代的时候,会对它有详细的介绍。
ensureExplicitCapacity方法中生成的目标数组大小和原来使用的数组大小进行了比较,当目标数组比原来的数组容量要大时,这时候就要使用grow方法扩容。
下面我们具体看一下grow方法的具体实现。
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);
}
在grow方法中,我们可以看到使用oldCapacity来记录原来数组的长度,用newCapacity记录扩容后数组的长度,而扩容后的数组长度是原来数组长度的1.5倍。然后判断扩容后的数组是否比目标数组大。最后使用Arrays工具类把旧数组的数据加到新数组里面。
remove方法
remove方法有两个多态实现,一个是通过int类型下标删除,一个是通过object元素匹配删除。
下面我们具体看一下通过index下标的remove方法的具体实现。
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; // clear to let GC do its work
return oldValue;
}
因为是通过index下标删除元素,首先通过rangeCheck方法检查下标是否合法。remove方法会对ArrayList进行修改,所以modCount要加1。然后我们把下标元素取出来,使用arraycopy方法对,把删除元素后面的元素前移一位,返回下标元素。
下面我们具体看一下rangeCheck方法的具体实现。
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
rangeCheck方法就是简单的对index下标进行判断,如果下标超出了size限制,那么就报出异常。
下面我们具体看一下通过object元素的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;
}
object首先判断object元素是否为空。若为空,使用for循环,遍历所有数组元素,如果数组元素为null,则使用fastRemove方法通过下标删除。若不为空,使用for循环,遍历所有数组元素,使用equals方法比较,如果相等则fastRemove通过下标删除元素。
下面我们具体看一下通过fastRemove方法的具体实现。
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
}
移除元素时,记录ArrayList的修改记录(modCount)加1。使用数组复制的方法,把删除元素的数组前移一位,原来的数组长度减一。
ArrayList中的迭代器实现
回到刚刚出现的modCount,这个是表示ArrayList的修改次数记录。那么它是ArrayList的私有属性吗?不,它是ArrayList的父类AbstractList的保护属性。初始值为0。
protected transient int modCount = 0;
同时在ArrayList中是没有获得迭代器的方法,获得迭代器的方法在它的父类AbstractList中有。
public Iterator<E> iterator() {
return new Itr();
}
在ArrayList的内部有一个内部类Itr,继承了Iterator接口,这个就是ArrayList的迭代器的具体实现类。
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
其中Itr这个迭代器内部类中有三个私有属性,cursor表示下一个元素下标,初始值为0;lastRet表示当前元素下标,初始值为-1;expectedModCount记录迭代器使用时,ArrayList当前的使用次数。
其中checkForComodification方法就是检测在迭代当中是否有其他线程修改了ArrayList集合。因为ArrayList集合是支持多线程的,线程是不安全的。所以必须通过这个expectedModCount和modCount之间修改次数的比较,来判断是否有多个线程正在修改ArrayList集合。这也就是Fail-Fast 机制(快速失败机制)。
因为有checkForComodification方法进行修改次数比较,所以在迭代的过程中,我们不能通过ArrayList的remove方法进行元素删除。但是Iterator迭代器中也有remove方法移除,使用迭代器中的方法移除元素就可以避免异常报错。但是这仅是针对单线程,当多线程同时访问时,应该使用CopyOnWriteArrayList并发容器代替ArrayList。
ArrayList总结
1、ArrayList的底层是Object数组实现的。
2、ArrayList的add、remove、数组扩容方法底层都是通过数组复制的方法实现。
3、ArrayList底层的Object数组初始容量是10,每次数组扩容,新数组的长度是原数组的1.5倍。
4、ArrayList的remove方法不论是通过下标还是元素删除,实际上都是通过for循环根据下标删除。
5、增强for循环底层也是使用迭代器实现的,使用迭代器时,不能使用list集合中的remove,add方法,因为会改变modCount值,导致expectedModCount和modCount不相等。
6、ArrayList是支持多线程的,因此它是线程不安全的集合。