ArrayList源码分析
其继承了AbstractList
类,并实现了Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess
这些接口
其不是线程安全的
Vector
是List
的古老实现类,其是线程安全的
一. 构造函数
1. 默认构造函数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
默认构造函数会初始化DEFAULTCAPACITY_EMPTY_ELEMENTDATA
这个空数组为保存数据的数组。
这样在之后的第一次add
操作时,会判断是否当时由默认构造函数构造,这样就会将10作为扩容的最小容量。
2. 初始化容量构造
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else {
if (initialCapacity != 0) {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
this.elementData = EMPTY_ELEMENTDATA;
}
}
若初始化容量大于0,则分配一个初始容量大小的Object
数组
若初始容量为0,则将EMPTY_ELEMENTDATA
赋值作为存储数据的数组
不直接新建一个空数组是为了后面扩容时分辨当时是使用默认构造函数构造还是参数为0的构造函数构造
3. 从给定集合构造
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((this.size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
this.elementData = a;
} else {
this.elementData = Arrays.copyOf(a, this.size, Object[].class);
}
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
若集合为空,则赋值EMPTY_ELEMENTDATA
二. 扩容
当向ArrayList
中添加元素的时候,若当前空间足够放下新的元素,则不用扩容,直接添加进数组中的相应位置,若当前空间大小不足够放下新的元素,则需要对原数组进行扩容,且为了减少扩容频率,每次不能只扩容刚好放下新元素的大小,而是需要以1.5倍的容量大小扩容。
1. add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
其中调用了ensureCapacityInternal
方法,调用该方法会决定是否需要扩容,调用之后才将新的数据添加到数组的末尾。
2. ensureCapacityInternal方法
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
在该方法中,会判断当时的构造方法,如果是无参数构造,即elementdata
为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
时,会以需求的最小容量与DEFAULT_CAPACITY
中的最大值作为新的最小容量,也就是这种情况下会从10开始每次1.5倍扩容。如果当初指定了size为0的构造函数,则最小容量不变,也就是会从1开始每次1.5倍扩容。
在该函数中会调用ensureExplicitCapacity
来判断是否需要进行扩容。
3. ensureExplicitCapacity方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在这里modCount
会加1,该变量用来标记ArrayList
实例的操作次数。
同时若需要扩容,则会调用最终的扩容函数,并传入实际需要的最小容量。
grow方法
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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;
}
在该方法中,会先计算现在的容量的1.5倍大小是否满足最小容量,这里使用位运算得到1.5倍结果。
如果不满足,则直接以最小需求容量作为扩容后的大小。
同时会判断新的数组大小是否超过预设的最大容量,使用hugeCapacity
方法判断并抉择,若最小需求容量比最大容量大则直接分配Integer.MAX_VALUE
大小的数组,否则分配预设最大容量的数组。
这里在
hugeCapacity
中用最小需求容量来比较是因为之前计算的新的数组大小可能是通过1.5倍运算得来的结果,这样其可能远远大于最小需求容量,也因此可能导致新的容量大于数组大小限制,因此使用最小需求容量重新计算新的数据容量
之后便使用Arrays.copyOf
函数复制原数组大小并分配新数组。
ensureCapacity方法
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
该方法是为了避免频繁扩容时,在将要添加大量元素之前进行调用,先进行一次扩容,以免后面的多次扩容影响效率。
三. modCount
在ArrayList
中的每次增删操作中,都会有一个modCount
变量自增,其记录了该ArrayList
实例的操作次数。
该变量来自于父类AbstractLIst
,其用于防止在迭代过程中的同时发生了结构性变化比如添加删除。
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount;
private Itr() {
this.expectedModCount = AbstractList.this.modCount;
}
public boolean hasNext() {
return this.cursor != AbstractList.this.size();
}
public E next() {
this.checkForComodification();
try {
int i = this.cursor;
E next = AbstractList.this.get(i);
this.lastRet = i;
this.cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException var3) {
this.checkForComodification();
throw new NoSuchElementException(var3);
}
}
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
AbstractList.this.remove(this.lastRet);
if (this.lastRet < this.cursor) {
--this.cursor;
}
this.lastRet = -1;
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
final void checkForComodification() {
if (AbstractList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
AbstractList
的迭代器由Itr
内部类实现,其有一个expectedModCount
保存期待的modCount
值。这样当两个值不一样时,说明该数组的元素个数发生了变化,继而抛出错误,但是使用迭代器的remove
方法就不会出现该问题。
测试代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
class demo {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
//CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
String str = (String) iterator.next();
if(str.equals("e")){
list.remove(str);
}else{
System.out.println(str);
}
}
}
}
这里使用ArrayList
的remove
方法,运行会抛出错误:
a
b
c
d
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at demo.main(Main.java:15)
但是换用iterator.remove()
就正常输出