ArrayList 简介
ArrayList 基于数组实现,继承 AbstractList ,实现 List ,RandomAccess ,Cloneable ,Serializable 接口,RandomAccess 接口标识着该类⽀支持快速随机访问
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
成员变量
(1)数组的默认大小为 10 ,从 Java1.7 开始,初始容量为 0
private static final int DEFAULT_CAPACITY = 10;
(2)存储数组
transient Object[] elementData;
(3)当前元素个数
private int size;
(4)最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
由注释 Some VMs reserve some header words in an array. Attempts to allocate larger arrays may result in OutOfMemoryError 可知,有些虚拟机会在数组中保存 header words ,尝试分配更大的数组可能会导致 OutOfMemoryError ,故留出 8 个空间
扩容机制
对于添加元素,使用 ensureCapacityInternal() 方法来保证容量足够,传入 size + 1 也就是该次添加元素所需的最小容量进行判断,若容量不够自动进行扩容,再将所添加的元素加在数组末端即可
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal() 方法中,调用 calculateCapacity() 方法修正将要扩至的容量,若当前数组为空,且所需最小容量小于默认值,则扩容至默认值,然后调用 ensureExplicitCapacity() 进行扩容处理
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
ensureExplicitCapacity() 方法中进行是否需要扩容的判断,若所需最小容量已经大于当前数组的长度,则调用 grow() 方法进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow() 方法如下:
- 获取扩容前数组容量,记为 oldCapacity
- 计算预计要扩至的容量,计算方法为 oldCapacity + (oldCapacity >> 1) ,也就是扩至原来的 1.5 倍左右,记为 newCapacity
- 若新容量 newCapacity 仍然小于所需最小容量 minCapacity(如果原容量为1,计算得新容量仍然为1,导致新容量小于所需最小容量,或是使用 addAll 一次性添加过多元素,导致扩容 1.5 倍后容量仍然不足),则直接令新容量等于所需最小容量
- 若新容量超过了定义的最大容量 MAX_ARRAY_SIZE ,则调用 hugeCapacity() 方法处理,获取修正后的新容量
- 调用 Arrays.copyOf() 把原数组整个复制到新数组中,此操作代价较高
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);
}
hugeCapacity() 中,如果所需最小容量都 minCapacity 超过了最大容量,则新容量则为 Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE ,即为 Integer.MAX_VALUE - 8
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
删除元素
- 调用 rangeCheck() 检查 index 是否越界
- 用 oldValue 记录将要删除的元素
- 计算需要移动的位数 numMoved
- 若 numMoved 大于 0 ,则调用System.arraycopy() 方法,将 index + 1 后面的元素都复制到 index 位置上(若删除的是最后一个元素,则不需要移动)
- 置空最后一个元素
- 返回 oldValue
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;
}
ArrayList 序列化问题
ArrayList 基于数组实现,具有动态扩容特性,数组空间不一定被完全使用,故无需序列化整个数组,使用 transient 修饰
ArrayList 中使用 writeObject() 和 readObject() 来控制只序列化数组中有元素填充的那部分内容
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
Fail-Fast 机制
modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException