ArrayList源码分析

211 阅读5分钟

源码分析

关键参数:

参数 名称 默认值
DEFAULT_CAPACITY 默认初始化容量 10
EMPTY_ELEMENTDATA 默认元素集合 {}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 无参构造实例默认元素集合 {}
elementData 存放元素的数组 {}
size 记录ArrayList中存储的元素的个数 0
modCount 修改次数 0
MAX_ARRAY_SIZE 最大数组长度 Integer.MAX_VALUE - 8

主要构造参数

  • public ArrayList(int initialCapacity) : 构造一个具有指定初始容量的空列表;
  • public ArrayList(): 构造一个初始容量为10的空列表;
  • public ArrayList(Collection<? extends E> c) : 构造一个包含指定集合的元素的列表。

基本方法

grow()

 // 扩容操作 
 // 扩展list以容下添加的元素
 // 有溢出意识的代码
 // 这里也详细说明了什么时候该扩容
 private void grow(int minCapacity) {  
        // 未添加元素之前的原始容量
        int oldCapacity = elementData.length;
        // 动态扩容,1.5倍的方式扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //当增加1.5倍后还是不够所需要长度,则直接用所需长度来扩容
        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);
    }

trimToSize()

// 缩容操作
// 当我们需要向ArrayList增加大量元素时它会扩容成一个大数组,当我们不需要那么多元素的时候把它删了。 ArrayList没有自动来处理这个问题,它提供了一个方法trimToSize()方法。我们可以手动调用此方法触发ArrayList的缩容机制。这样就可以释放多余的空间,提高空间利用率。
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        // size为0,表示数组中没有元素,直接设成空数组就行,以size不为0,则用当前数组中元素的size来缩容,整个数组长度设为size的大小
        elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            : Arrays.copyOf(elementData, size);
    }
}

indexOf(Object o) And lastIndexOf(Object o)

// 返回指定元素在此列表中首次出现的索引
// ArrayList可以存放null值
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

// 返回指定元素在此列表中最后出现的索引
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

get(int index)

// 获取指定索引的数据
public E get(int index) {
    // 检查索引参数是否查过list的最大size,超过直接报异常
    rangeCheck(index);
    return elementData(index);
}
    
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

set(index, E element)

public E set(int index, E element) {
    // 同样要检查索引是否越界
    rangeCheck(index);
	// 保存数组的index下的旧值,set成功返回参数是先前位于index的元素
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
    

add()

// 两种add的方式: 尾部添加和索引添加
// 你会发现modCount 只在线程不安全的类里面出现,比如:ArrayList,HashMap等,主要用处是在一个迭代器初始的时候会赋予它调用这个迭代器的对象的modCount,如果在迭代器遍历的过程中,一旦发现这个对象的modCount和迭代器中存储的modCount不一样那就抛异常(Fail-Fast机制)
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 增加 modCount
    elementData[size++] = e;
    return true;
}

public void add(int index, E element) {
    rangeCheckForAdd(index);  // 检查索引是否越界

    ensureCapacityInternal(size + 1);  // 增加 modCount
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

remove()

// 同样两种移除方式:移除索引元素和移除obj元素
public E remove(int index) {
    rangeCheck(index);  // 越界问题

    modCount++;  // 凡事有修改的方法,modCount都需要加1
    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;
}

// 移除第一个符合的元素
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;
}

// 移除步骤解耦出来
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
}

writeObject和readObject

// 也就是ArrayList对象的序列化和反序列化
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // 写出元素计数以及任何隐藏的内容
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // 写出大小作为与clone()行为兼容的容量
    s.writeInt(size);

    // 按照正确的顺序写出所有元素。
    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;

    // 阅读大小以及任何隐藏的内容
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // 像clone()一样,根据大小而不是容量分配数组
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // 按正确的顺序读入所有元素。
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

除了上述的一些基本的核心方法外,ArrayList中还有几个内部类,分别是实现了Iterator和ListIterator的两个内部类以及一个实现随机存取的SubList类,但是无论是迭代器也好随机存取也好,其内部实现和上面的方法都比较类似,区别可能比较大的地方在与迭代器中都有modCount的计数器,每次比较全局计数器和自己现成的计数器是不是有区别,有的话产生fast-fail机制并抛出ConcurrentModifactionException,也就是多线程下有线程在别的线程使用迭代器修改了一些东西,就会抛出异常防止越界等问题的出现。