深入解析ArrayList底层源码


        ArrayList是Java中常用的数据结构之一,它实现了动态数组的特性,能够高效地存储和管理元素集合。在本系列中,我们将深入探究JDK 1.8版本中ArrayList的源码。

1. 数据结构

        ArrayList内部使用数组作为底层数据结构,通过数组的索引来访问和操作元素。具体来说,ArrayList的实例变量elementData就是一个Object类型的数组,用于存储元素。这个数组的长度即为ArrayList的容量。

// 存储元素的数组
transient Object[] elementData;

2. 构造函数

        ArrayList提供了多个构造函数,以满足不同使用场景。其中,无参构造函数用于创建一个初始容量为0的空数组,而带有初始容量参数的构造函数可以创建指定容量大小的数组。

// 默认构造函数
public ArrayList() {
    // 初始化为共享的空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 带初始容量参数的构造函数
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 创建指定容量大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 抛出异常,初始容量不能小于0
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

3. 确保容量足够

        在添加元素时,ArrayList需要确保内部数组的容量足够,以存放新的元素。为了实现这一点,ArrayList内部维护了ensureCapacityInternal()和ensureExplicitCapacity()两个方法。

// 确保容量够用
private void ensureCapacityInternal(int minCapacity) {
    // 对于空数组,使用默认容量或指定最小容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 检查并明确容量
    ensureExplicitCapacity(minCapacity);
}

// 明确容量
private void ensureExplicitCapacity(int minCapacity) {
    // 记录修改次数
    modCount++;
    // 如果需要扩容,执行扩容操作
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

4. 添加元素的实现

        ArrayList的核心方法之一是add(),用于向列表末尾添加元素。添加元素的步骤包括确保容量足够,然后将新元素放入elementData数组的末尾,并增加列表的大小。

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

5. 删除元素的实现

        ArrayList提供了多个删除元素的方法,例如remove(int index)和remove(Object o)。删除元素时,需要将被删除元素之后的元素向前移动,以填补删除位置。

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);
    }
    
    // 将最后一个位置设置为null,帮助垃圾回收
    elementData[--size] = null;
    
    return oldValue;
}

6. 数组拷贝和线程安全性

        当需要扩容或删除元素时,ArrayList会调用System.arraycopy()方法来实现数组的拷贝。然而,由于数组的拷贝是一个耗时操作,这可能会影响性能。

        需要注意,ArrayList并不是线程安全的,这意味着如果多个线程同时访问并修改同一个ArrayList实例,可能会导致数据不一致或意外的行为。如果需要在多线程环境中使用ArrayList,需要采取适当的同步措施或选择线程安全的集合。

7. 扩容机制

        在添加元素时,如果当前容量不足以容纳新元素,ArrayList会执行扩容操作以增加容量。这是通过grow()方法实现的,以下是它的代码和注释:

// 扩容操作
private void grow(int minCapacity) {
    // 计算新容量,扩展为旧容量的1.5倍
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果新容量不足以容纳所需容量,直接使用所需容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 处理溢出情况
    if (newCapacity - Integer.MAX_VALUE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 使用Arrays.copyOf()复制元素到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

8. 线程安全性

        需要注意的是,ArrayList并不是线程安全的。这意味着如果多个线程同时访问和修改同一个ArrayList实例,可能会导致数据不一致或意外行为。为了在多线程环境下使用ArrayList,需要采取适当的同步措施。

如果在多线程环境中使用ArrayList,以下是几种可能的解决方案:

        使用显式锁(如ReentrantLock)来保护访问和修改操作。
        使用Collections.synchronizedList()方法将ArrayList转换为线程安全的集合。
        使用CopyOnWriteArrayList,它是线程安全的,适用于读多写少的场景。

// 将ArrayList转换为线程安全的集合
List<E> synchronizedList = Collections.synchronizedList(new ArrayList<>());

// 使用CopyOnWriteArrayList
List<E> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

9. 迭代器的实现

        ArrayList实现了java.util.Iterator接口,使我们能够通过迭代器逐个遍历集合中的元素。以下是ArrayList迭代器的核心部分代码,以及对其关键部分的注释:

private class Itr implements Iterator<E> {
    int cursor;       // 游标,指向下一个元素
    int lastRet = -1; // 上一个返回的元素索引
    int expectedModCount = modCount;

    public boolean hasNext() {
        // 检查游标是否达到列表的末尾
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        // 检查在迭代期间是否有其他修改操作
        checkForComodification();
        
        // 获取游标指向的元素,并移动游标到下一个位置
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
    
    // ...
}

10. 性能考虑

        在使用ArrayList时,考虑性能问题至关重要,以确保代码的高效运行。以下是一些关于性能方面的考虑:

        尽量避免在迭代期间对ArrayList进行修改操作,因为这可能导致ConcurrentModificationException异常。
        在需要遍历所有元素的情况下,使用增强型for循环(for-each循环)会更加简洁和高效。
        在迭代过程中,ArrayList的随机访问时间复杂度为O(1),因此遍历元素时的性能非常好。

11. 比较和排序

        ArrayList提供了sort()方法,用于对列表中的元素进行排序。排序操作使用Arrays.sort()方法,实现了快速排序算法。若要对元素进行排序,我们可以按照以下方式调用sort()方法:

// 使用默认的排序规则(元素必须实现Comparable接口)
public void sort(Comparator<? super E> c) {
    Arrays.sort((E[]) elementData, 0, size, c);
}

12. 使用建议

        在实际应用中,以下是一些使用ArrayList的建议:

        在需要频繁随机访问元素的情况下,ArrayList是一个不错的选择,因为它支持O(1)时间复杂度的随机访问。
        考虑在初始化时指定初始容量,以避免频繁的扩容操作,从而提升性能。
        如果需要对元素进行排序,可以使用ArrayList的sort()方法,或者考虑使用Collections.sort()对ArrayList进行排序。
        注意ArrayList在多线程环境下的线程安全问题,如果需要在多线程环境中使用,可以考虑使用线程安全的集合类。

// 将ArrayList转换为线程安全的集合
List<E> synchronizedList = Collections.synchronizedList(new ArrayList<>());

// 使用CopyOnWriteArrayList
List<E> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值