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