ArrayList源码解析与性能优化

ArrayList源码解析与性能优化

ArrayList是Java集合框架中最常用的动态数组实现之一,它提供了动态扩容、随机访问等特性,适用于大多数场景。

1. ArrayList简介

ArrayList实现了List接口,基于动态数组实现。它允许存储任意类型的元素,并根据需要动态调整容量。在添加、删除元素时,ArrayList会自动进行扩容和缩容操作。

为保证效率,ArrayList没有实现同步(synchronized)为非线程安全集合,如需并发访问,用户需手动同步,也可使用Vector代替。

2. ArrayList类结构

2.1 类层次结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // ...
}
  • ArrayList继承自AbstractList,实现了List接口。
  • 实现了RandomAccess接口,表示支持快速随机访问。
  • 实现了Cloneable接口,表示可以进行克隆。
  • 实现了Serializable接口,表示支持序列化。

2.2 常量与变量

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

// 用于空ArrayList实例的共享空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用于默认大小的空ArrayList实例的共享空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储ArrayList元素的数组缓冲区
transient Object[] elementData;

// ArrayList实际元素数量
private int size;

3. ArrayList构造方法

3.1 默认构造方法

// 无参构造方法,创建一个空的ArrayList
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

3.2 指定初始容量构造方法

// 指定初始容量的构造方法
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);
    }
}

4. ArrayList扩容机制

ArrayList在添加元素时会自动进行扩容,其扩容机制如下:

// 添加元素
public boolean add(E e) {
    // 确保容量足够
    ensureCapacityInternal(size + 1);
    // 将元素添加到数组末尾
    elementData[size++] = e;
    return true;
}

// 确保容量足够
private void ensureCapacityInternal(int minCapacity) {
    // 扩容
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算数组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
     // 如果数组是空数组,取默认容量和minCapacity的较大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

// 扩容
private void ensureExplicitCapacity(int minCapacity) {
    // 记录修改次数
    modCount++;

    // 如果minCapacity大于数组容量,需要进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 扩容策略
private void grow(int minCapacity) {
    // 当前数组容量
    int oldCapacity = elementData.length;
    // 新数组容量,扩容为原容量的1.5倍 oldCapacity >> 1 右移:向右除以2的移动位数次幂
    int newCapacity = oldCapacity + (oldCapacity >> 1);

    // 如果新容量仍然小于minCapacity,则使用minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    // 如果新容量超过最大数组容量,使用Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // 复制数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

注意:

ArrayList的自动扩容时会将老数组中的元素重新拷贝一份到新的数组中,并且扩容的大小为原容量的1.5倍。

5. ArrayList性能优化建议

5.1 指定初始容量

在创建ArrayList时,如果能够预估元素的数量,最好通过构造方法指定初始容量,避免动态扩容带来的性能开销。

List<String> list = new ArrayList<>(1000);

5.2 批量操作时设置初始容量

如果事先知道要添加的元素数量很大,最好在创建ArrayList时设置一个足够大的初始容量,避免多次扩容。

List<String> list = new ArrayList<>(10000);
list.addAll(anotherList);

5.3 ensureCapacity方法手动扩容

根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量

public void ensureCapacity(int minCapacity) {
    // 获取最小容量
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        ? 0
        : DEFAULT_CAPACITY;
    // 如果minCapacity大于数组最小容量,则进行扩容
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

5.4 使用Iterator遍历

在遍历ArrayList时,最好使用Iterator而不是通过索引访问元素。因为通过索引访问元素的方式在LinkedList上效率较高,但在ArrayList上会产生不必要的性能开销。

List<String> list = new ArrayList<>();
// 不推荐
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
// 推荐
for (String s : list) {
    System.out.println(s);
}

5.5 使用subList避免大量截取

ArrayList上使用subList方法生成子列表时,尽量避免在大量数据上进行截取。因为subList方法返回的子列表并不是一个新的ArrayList,而是原列表的一个视图,操作子列表可能会影响到原列表。

List<String> list = new ArrayList<>();
List<String> subList = list.subList(0, 10);

你可能感兴趣的:(Java,java)