2.ArrayList源码深度解读

76 阅读4分钟

一、构造方法

有三个重载的构造方法

1.直接newArrayList<>()的时候调用的时候就是调的这个构造方法,里面初始化了一个空数组,elementData是个Object数组

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2.传入初始化容量的时候,initialCapacity大于0就用传入的值初始化数组容量,initialCapacity=0的时候跟无参构造方法的效果一样是初始化一个空数组,initialCapacity<0的时候不合法

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

3.传入一个Collection,先把传入的集合转为一个Object数组,
(1)如果数组大小大于0,则判断集合的类型是否为ArrayList,如果是arrayList则直接初始化elementData为传入的集合,否则把传入集合的内容拷贝到elementData
(2)如果数组大小等于0,则初始化一个空数组

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

二、添加元素

1.调用add方法,在已有元素末尾插入,modCount记录元素修改次数+1,

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

然后调用内部的重载的私有add方法add(E e, Object[] elementData, int s),传入参数为(插入的元素,存储数据的数组,插入的位置),如果插入的位置等于数组长度,则进行扩容,否则将指定位置元素赋值为传入的值e,然后把数组元素数量+1

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

扩容方法grow,调用重载的方法grow(int minCapacity),先拿到旧的容量oldCapacity,如果旧的容量大于0或者数组元素不为空,则将数组容量扩容为原来的1.5倍(没超过SOFT_MAX_ARRAY_LENGTH的情况下),然后拷贝元素到新数组中,再把新的数组赋值给elementData;如果数组原来的元素为空,则初始化数组长度为DEFAULT_CAPACITY=10

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

private Object[] grow() {
    return grow(size + 1);
}

为什么是扩容为1.5倍?答案就在newLength方法里面,假设旧的容量是10,newLength的三个参数的值分别为10,1,5,newLenth方法实现,prefLength = 10 + Math.max(1, 5) = 15,这是没超过SOFT_MAX_ARRAY_LENGTH的情况下,超过SOFT_MAX_ARRAY_LENGTH就要走hugeLength方法

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // preconditions not checked because of inlining
    // assert oldLength >= 0
    // assert minGrowth > 0

    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // put code cold in a separate method
        return hugeLength(oldLength, minGrowth);
    }
}

hugeLength方法:
(1)如果扩容后溢出,就是超出Integer.MAX_VALUE,则抛出异常
(2)如果小于SOFT_MAX_ARRAY_LENGTH,则返回SOFT_MAX_ARRAY_LENGTH
(3)第三种情况,返回扩容后的长度

private static int hugeLength(int oldLength, int minGrowth) {
    int minLength = oldLength + minGrowth;
    if (minLength < 0) { // overflow
        throw new OutOfMemoryError(
            "Required array length " + oldLength + " + " + minGrowth + " is too large");
    } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
        return SOFT_MAX_ARRAY_LENGTH;
    } else {
        return minLength;
    }
}

关于SOFT_MAX_ARRAY_LENGTH的说明:值等于Integer.MAX_VALUE - 8,为什么是这个值?为了尽可能避免OOM,实际使用情况还要看对象的大小,可能到不了这个值就OOM了

public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

三、删除元素

1.删除指定元素,先遍历数组,找到元素的位置i

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

然后调用fastRemove删除:fastRemove先记录修改次数+1,然后从i+1开始元素整体往前移动一位,最大位置上的元素置为null

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

四、修改指定位置的元素

1.调用set方法,直接将对应位置的元素改为指定值,然后返回旧的元素

public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

五、查找指定位置元素

1.调用get方法,返回数组对应位置元素

public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}