【Java集合框架】3 - 4 ArrayList 源码分析

§3-4 ArrayList 源码分析

3-4.1 ArrayList 集合

ArrayListList 接口的一个实现类,其本质是一个可变长的数组。

基于这种实现关系,ArrayList 类对象可以调用 List 以及 Collection 接口的方法。

该类并不同步,因此早多线程中使用同一个 ArrayList 实例,且至少一个线程对该列表做了结构性修改,则必须外部同步。

结构性修改指的是添加或删除一个或多个元素、或显式重新调整数组大小的操作,仅仅修改元素的值并不是结构性修改。

由于该实现类中的方法大多重写自其所实现的接口以及父接口,此处不过多赘述。该类方法中最主要的操作就是增删改查。

3-4.2 ArrayList 源码解析

ArrayList 的底层原理

  • 使用无参构造创建的 ArrayList 实例,默认创建的是长度为零的数组

    ArrayList 实例中的数组由成员 elementData 存储,该无参构造器的实现为:

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

    DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是内部定义的一个静态常数组,其长度为零,用于给默认大小空实例中的数组赋值。

    ArrayList 中有另外定义了一个长度为零的静态常空数组 EMPTY_ELEMENTDATA,该数组用于空实例,不同于前者。

  • 此时,添加第一个数据,底层会创建一个新的长度为 10 的数组

    类中还定义了一个整型变量 size,用于记录当前数组中元素个数,并指定下一次存放元素的位置;

    add(E e) 方法实现:

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

    私有变量 modCount 来自于抽象类 AbstractList,用于记录列表被结构性修改的次数。该字段由 iteratorlistIterator 方法返回的迭代器和列表迭代器使用。若该字段的值意外改变,(列表)迭代器则会在调用 next, remove, previous, setadd 方法时抛出异常 ConcurrentModificationException

    方法底层调用了另外一个私有的多参数重载 add 方法,该方法的实现为:

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

    使用无参构造所创建的集合对象中,size0elementData 是一个长度为零的数组,满足选择分支中的条件语句,执行 grow 方法扩容数组,该方法的实现为:

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

    内部又调用了另外一个有参重载,其实现为:

    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)];
        }
    }
    

    形参表示扩容后数组的理论最小容量,首次添加元素,则 minCapacity == 1

    方法会先记录原数组的旧容量,然后判断,由于调用的是无参构造,旧容量为 0,直接来到 else 分支,返回一个新的数组;

    新数组的长度取 DEFAULT_CAPACITY(默认容量)和最小容量的最小值,而前者定义在类中,为 10

    因此,对于无参构造获得的集合,首次添加元素(一个)会将数组扩容至长度 10grow 方法结束,回到 add 方法,将该元素添加进数组,并让 size 更新(为 1)。至此,首次添加元素结束;

  • 数组存满时,数组容量会扩容 1.5 倍

    数组存满时,仍然会通过上述流程执行有参 grow 方法;

    以旧容量为 10 为例,先记录旧容量,由于旧容量此时不为 0,短路或运算结果为 true,进入 if 选择分支;

    此时,通过 newLength 方法计算数组的新容量,该方法实现为:

    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);
        }
    }
    

    其中,SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8 = 2_147_483_639

    prefLength,即首选长度通过计算变为 15 = 10 + max(1,10 >> 1) = 10 + max(1,5) = 10 + 5,且满足第一个分支语句,直接返回,作为新数组的新容量;

    接着,再通过 Arrays.copyOf 方法,拷贝原始数组到一个具有新容量的数组中,并将该新数组返回;

    grow 方法结束,在新数组中添加该元素,并更新 size(为 11),添加完成;

  • 若一次添加多个元素,1.5 倍扩容仍然不足,则新数组长度以实际为准

    添加多个元素,往往用的可能是 addAll 方法,其实现为:

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }
    

    方法首先会将入参的集合使用接口中的 toArray 方法,将集合中的内容转换为待添加数组,并记录该待添加数组的长度;

    这里设待添加数组长度为 100,远远大于 1.5 倍的扩容大小,满足 if 分支中的表达式要求,调用有参 grow 扩容;

    grow 中的 if 分支中,调用相同方法计算新容量(newLength(15, (11 + 100) - 15, 7)),返回的 newCapacity = prefLength = 15 + max(96, 7) = 111

    然后,拷贝数组,将新数组返回后,添加新元素,更新 size(为 111),返回 true,表示成功添加,方法结束;

posted @ 2023-08-04 22:32  Zebt  阅读(19)  评论(0)    收藏  举报