Java源码分析——ArrayList

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

猜你喜欢

转载自blog.csdn.net/qq_25274377/article/details/120496307