ArrayList源码分析
数据结构
底层的数据结构就是数组,数组元素类型为Object类型
源码分析
一、类的继承关系
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
说明:ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。
接口讲解
二、成员变量
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
private int size;
private static final Object[] EMPTY_ELEMENTDATA = {};
/**用于默认大小的空实例的共享空数组实例。我们将其与空元素数据区分开,以知道何时充气添加第一个元素*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
成员变量 transient关键字
节省空间
因为elementData里面不是所有的元素都有数据,因为容量的问题,elementData里面有一些元素是空的,这种是没有必要序列化的。ArrayList的序列化和反序列化依赖writeObject和readObject方法来实现。可以避免序列化空的元素。
成员变量 2个空数组意义
//list1.elementData为EMPTY_ELEMENTDATA,
ArrayList list1 = new ArrayList(0);
//list2.elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
ArrayList list2 = new ArrayList();
添加首个元素时,扩容区别
list1 扩容为 1
list2 扩容为 DEFAULT_CAPACITY = 10
线程安全
只有当 ArrayList 作为共享变量时,才会有线程安全问题,当 ArrayList 是方法内部局部变量时,是没有线程安全问题的。
本质: ArrayList 自身的 elementData、size、modCount 在进行各种操作时,都没有加锁,而且这些变量的类型并非是可见的(volatile)的,所以如果多个线程对这些变量进行操作时,可能会有值被覆盖的情况。
三、构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { // 初始容量大于0
this.elementData = new Object[initialCapacity]; // 初始化元素数组
} else if (initialCapacity == 0) { // 初始容量为0
this.elementData = EMPTY_ELEMENTDATA; // 为空对象数组
} else { // 初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
// 无参构造函数,设置元素数组为空
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) { // 集合参数构造函数
elementData = c.toArray(); // 转化为数组
if ((size = elementData.length) != 0) { // 参数为非空集合
if (elementData.getClass() != Object[].class) // 是否成功转化为Object类型数组
elementData = Arrays.copyOf(elementData, size, Object[].class); // 不为Object数组的话就进行复制
} else { // 集合大小为空,则设置元素数组为空
this.elementData = EMPTY_ELEMENTDATA;
}
}
四、新增和扩容
新增主要分为两步:
- 判断是否需要扩容,如果需要则执行扩容操作;
- 赋值新元素。
add函数
public boolean add(E e) {
// 确保数组大小是否足够,不够则执行扩容,size 为当前数组的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
// 直接赋值
elementData[size++] = e;
return true;
}
扩容步骤(ensureCapacityInternal)源码:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算 capacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 无参构造 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 确保容量足够
private void ensureExplicitCapacity(int minCapacity) {
// 记录数组被修改
modCount++;
// 如果我们期望的最小容量大于目前数组的长度,则执行扩容操作
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容,并把现有数据拷贝到新的数组中去
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新的容量为旧的容量的 1.5 倍(capacity >> 1 ==> capacity / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的容量小于我们期望值 minCapacity,则将新的容量值赋值为期望值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新的容量大于 JVM 所能分配的数组的最大值,那么就用 Integer 的最大值
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);
}
注意点
- 扩容的规则并不是翻倍,而是 原来容量大小 + 原来容量大小的一半,直白来说,扩容后的大小是原来容量的 1.5 倍;
- ArrayList 中数组的最大容量是 Integer.MAX_VALUE,超过这个值,JVM 就不会给数组分配内存空间了;
- 新增时,并没有对值进行严格的校验,所以 ArrayList 是允许 null 值的。
扩容的本质
Arrays.copyOf(elementData, newCapacity)
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
五、删除和缩容
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);
// 赋值为空,有利于进行GC
elementData[--size] = null;
// 返回旧值
return oldValue;
}
缩容的本质
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
fail-fast 机制
fail-fast 机制是java集合(Collection)中的一种错误机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。
一种理念,在进行系统设计时优先考虑异常情况
什么时候会出现fail-fast?
以下两种情况下会导致fail-fast,抛出ConcurrentModificationException
- 单线程环境
遍历一个集合过程中,集合结构被修改。注意,listIterator.remove()方法修改集合结构不会抛出这个异常。 - 多线程环境
当一个线程遍历集合过程中,而另一个线程对集合结构进行了修改。
原理
// AbstractList中唯一的属性
// 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1
protected transient int modCount = 0;
// ArrayList 代码
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。
fail-fast 解决方式
使用java.util.concurrent包下的类去取代java.util包下的类
例如:private static List list = new ArrayList();
替换为:private static List list = new CopyOnWriteArrayList();