集合类源码解析——ArrayList

类图

成员变量

  • DEFAULT_CAPACITY

    默认初始化容量,代表 elementData 数组的长度

  • EMPTY_ELEMENTDATA

    构造空 ArrayList 实例时,赋值给 elementData 的空数组实例

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    构造默认容量 ArrayList 实例时,赋值给 elementData 的空数组实例

  • elementData

    ArrayList 实际存储元素的动态数组

  • size

    ArrayList 中实际容纳元素的个数

构造函数

public ArrayList(int initialCapacity)

 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //当赋值给 ArrayList 的容量为 0 时,令 elementData = EMPTY_ELEMENTDATA; 
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
}
复制代码

public ArrayList()

public ArrayList() {
    	//当构造默认容量的 ArrayList 时,令 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

public ArrayList(Collection<? extends E> c)

public ArrayList(Collection<? extends E> c) {
    	// 将源集合的数据转化为 array 赋值给 elementData
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class) {
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
}
复制代码

重要方法

add(E e)

public boolean add(E e) {
        // 保证添加元素后, ArrayList 不溢出
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
复制代码

该方法调用了 ensureCapacityInternal 保证扩容后不会溢出。

ensureCapacityInternal(int minCapacity)

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
复制代码

calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}
复制代码

该方法会返回添加元素后最小的 capacity 的值。如果初始化 ArrayList 时未指定容量,那么这个最小值将会是 DEFAULT_CAPACITY

ensureExplicitCapacity(int minCapacity)

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        if (minCapacity - elementData.length > 0) {
            //如果添加元素后,数组的容量会大于现有数组的 length。需要将数组扩容
            grow(minCapacity);
        }
}
复制代码

这里维护了 modCount 的改变。

grow(int minCapacity)

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 1. 当 ArrayList 第一次放入 元素时,扩容后新的数组容量为 1 或者 10;
        // 2. 当 ArrayList 添加元素后容量大于 MAX_ARRAY_SIZE 时,容量会变为 MAX_ARRAY_SIZE 或 Integer.MAX_VALUE
        // 3. 其它情况下,扩容操作使数组容量变为原来的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        // 若新增元素后,oldCapacity * 1.5 > MAX_ARRAY_SIZE,调用 hugeCapacity 方法计算容量
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            newCapacity = hugeCapacity(minCapacity);
        }
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
复制代码

这里看到 ArrayList 扩容后新数组的容量有三种情况。所以面试被问到 ArrayList 扩容后容量怎样变化时,最好将三种情况都说出来,而不只是回答变为原容量的 1.5 倍。

add(int index, E element)

    public void add(int index, E element) {
        // add 方法的索引范围校验
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);
        // 调用 System.arraycopy 将数组索引不小于 index 的元素后移
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        elementData[index] = element;
        size++;
    }
复制代码

remove(int index)

    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);
        }
        // 将最后一位元素置空,便于 GC 回收
        elementData[--size] = null;

        return oldValue;
    }
复制代码

remove(Object o)

    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;
    }
复制代码

分为空元素和非空元素两种情况遍历。

删除 ArrayList 中出现的第一个等于 o 的元素,查找到时,调用 fastRemove 方法删除。

fastRemove 方法移除了索引越界检查以及返回值的保存。

removeAll(Collection<?> c)

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
复制代码

调用批量删除方法 batchRemove,注意这里传入的布尔类型参数值为 false

batchRemove(Collection<?> c, boolean complement)

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        // r 表示 elementData 中待读取的下一个数据对应的索引;w 表示待写入的下一个索引
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++) {
                //  如果是删除集合中的元素,则将未出现在 c 中的元素写入到索引为 w 的位置;否则将出现在 c 中的元素写入到索引为 w 的位置。
                if (c.contains(elementData[r]) == complement) {
                    elementData[w++] = elementData[r];
                }
            }
        } finally {
            // 保留与 AbstractCollection 行为的兼容性
            if (r != size) {
                System.arraycopy(elementData, r,
                        elementData, w,
                        size - r);
                w += size - r;
            }
            if (w != size) {
                // 将已写入元素之后的元素置为 null
                for (int i = w; i < size; i++) {
                    elementData[i] = null;
                }
                // modCount 增加次数为 删除的元素个数
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
复制代码

该方法设计的很巧妙。

  1. 设置了 rw 两个变量来控制数组的已读标记和写入标记。r 表示已经遍历过的数据的索引,w 表示已写入的数据的索引。(个人感觉类似于 NettyByteBuf 的设计)

  2. 通过 complement 变量来控制写入的数据是否是集合 c 中出现过的数据。如果 complement == true,则写入的元素为该 ArrayList 实例 与集合 c 中共同存在的元素;如果 complement == false,则写入的元素为该实例元素去除集合 c 中元素的结果。这里是用来删除集合 c 中的元素,所以传入的变量值为 false

  3. finally 块中,将 w 及之后的元素都置为 null。只留下符合要求的元素。同时,更新 modCount 的次数。

set(int index, E element)

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
复制代码

修改操作很简单,在数组赋值的基础上增加了索引值校验,注意该方法的返回值为该索引位上的旧值

get(int index)

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
复制代码

查找操作同修改操作一样,在数组查找操作上增加索引校验。

其他方法

clone()

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
复制代码

这里元素的拷贝调用了 Arrays.copyOf 方法,该方法是浅拷贝,所以 clone 方法呈现的结果也会是浅拷贝,对于拷贝出来的元素进行修改,会同时修改原 ArrayList 中的元素。

总结

ArrayList 的方法都比较简单,但是其中也有不少细节需要注意。

ArrayList 底层实现是动态数组,所以特点是元素的查找操作快,在末尾插入和删除元素也很快,时间复杂度都是 O(1)。而在中间插入和删除元素的均摊时间复杂度为 O(n) 。

ArrayList 扩容时,其中的 elementData 容量变化会有三种情况:

      1. 当 `ArrayList` 第一次放入 元素时,扩容后新的数组容量为 1 或者 10;
      2. 当 `ArrayList` 添加元素后容量大于 `MAX_ARRAY_SIZE` 时,容量会变为 `MAX_ARRAY_SIZE` 或 `Integer.MAX_VALUE`
      3. 其它情况下,扩容操作使数组容量变为原来的 1.5 倍
复制代码

ArrayListclone 方法是浅拷贝。对拷贝出来的实例中的元素进行修改,会改变原来的 ArrayList 实例。

猜你喜欢

转载自juejin.im/post/5d9d8ee85188251fdf58e7b9
今日推荐