Java 常用集合类 ArrayList

ArrayList 整体设计

ArrayList 是 List 中最常用的一个类,继承抽象父类 AbstractList。ArrayList 是一个数组队列,相比于 Java 数组,ArrayList 的容量可以动态增长。

ArrayList 采用数组 Object[] 作为数据存储结构。

属性和构造函数

// 默认初始化容量(10)
private static final int DEFAULT_CAPACITY = 10;

// 元素存储
transient Object[] elementData;

// 当前大小
private int size;

// 创建一个初始容量为 initialCapacity 的 ArrayList
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);
    }
}

// 创建一个包含 collection 的 ArrayList
public ArrayList(Collection<? extends E> c) {
    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 {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

默认初始化数组大小是 10

add 方法

ArrayList 的 get() 方法比较简单,根据索引返回返回数组指定位置的数据, 首先会检查数组是否越界。

ArrayList 的 add() 方法涉及到数组的扩容。

public boolean add(E e) {
    // 检查数组容量
    // 这一操作会修改 modCount
    ensureCapacityInternal(size + 1); 
    // 添加到数组中
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
        // 检查数组容量
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    // 修改 modCount
    modCount++;

    // 容量不足需要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

ArrayList 也可以指定位置(index)添加元素

public void add(int index, E element) {
    rangeCheckForAdd(index);

    // 检查数组容量(可能会执行扩容操作)
    ensureCapacityInternal(size + 1);  

    // 通过 arraycopy 将原位置以及之后的数据整体 向后 移动一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 添加到数组中
    elementData[index] = element;
    size++;
}

指定位置添加元素需要将该位置及之后的数据在数组中移动一位,类似的 remove() 方法将移除位置之后的数据向前移动一位。

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

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 通过 arraycopy 将原位置以及之后的数据整体 向前 移动一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 数据向前移动一位后,数组的最后一位置为空
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

所以 ArrayList 在中间插入数据操作会比较影响效率,并且频繁的插入可能导致扩容的额外消耗,如果能够预估数据量的大小,建议尽量初始化时设置容量。

grow 方法

ArrayList 扩容逻辑:默认扩容原数组长度的一半,也就是新的数组大小是原来的 1.5 倍

计算公式:int newCapacity = oldCapacity + (oldCapacity >> 1);

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 计算新的容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 进行数组扩容(通过拷贝的方式)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

clone 方法

ArrayList 实现了 Cloneable 接口,只是拷贝了这个 List 对象,而没有拷贝 List 中的元素,这意味着两个 List 持有的是同一组对象

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        // 将当前 ArrayList 的全部元素拷贝到v中
        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);
    }
}

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注