ArrayList
是 Java 中非常常用的一个集合类,它实现了 List
接口,并且底层是基于 动态数组(也叫 可变大小数组)来存储数据的。它提供了对元素的快速随机访问,并且可以动态地增加容量。
1. 基本结构
ArrayList
类位于 java.util
包中,继承了 AbstractList
并实现了 List
接口。ArrayList
底层维护了一个数组,通过这个数组来存储集合中的元素。其最重要的成员变量是 elementData
,它是一个对象数组,存储了 ArrayList
中的元素。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8683452581122892189L;
// 该数组存储集合中的元素
transient Object[] elementData;
// 当前元素的个数
private int size;
// 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ARRAY = {};
public ArrayList() {
this.elementData = EMPTY_ARRAY;
}
}
}
2. 构造函数
ArrayList
提供了多个构造函数:
- 默认构造函数:当没有传递初始容量时,
ArrayList
会初始化一个容量为 10 的默认数组。 - 带有初始容量的构造函数:可以指定一个初始容量,
- 非负容量:这个构造函数确保了用户不会传入负数作为容量,因为负数容量没有意义,抛出异常可以避免不正确的初始化。
- 容量为 0:当容量为 0 时,直接使用一个空数组
EMPTY_ELEMENTDATA
,而不是分配一个新的对象数组。这可以节省内存开销。实际上,ArrayList
是通过动态扩容来增加容量的,起始容量 0 不会影响后续的正常使用。 - 初始容量大于 0:允许用户根据需求初始化一个足够容量的
ArrayList
,避免多次扩容,提高性能。
public ArrayList(int initialCapacity) {
// 如果初始容量大于 0
if (initialCapacity > 0) {
// 为 `elementData` 数组分配指定容量的内存空间
this.elementData = new Object[initialCapacity];
}
// 如果初始容量等于 0
else if (initialCapacity == 0) {
// 设置 `elementData` 为一个空的静态常量数组
this.elementData = EMPTY_ELEMENTDATA;
}
// 如果初始容量小于 0,抛出异常
else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
3.添加元素
1. private void add(E e, Object[] elementData, int s)
这个方法是一个私有的辅助方法,主要用来向 ArrayList
添加元素。它接收以下参数:
e
:要添加的元素。elementData
:当前存储元素的数组。s
:当前ArrayList
中的元素数量。
功能:
- 如果当前数组
elementData
已满(即s == elementData.length
),则通过调用grow()
方法扩容数组。 - 然后将元素
e
添加到数组的s
位置。 - 更新
size
,表示当前ArrayList
的大小,size = s + 1
。
这段代码表示了向数组末尾添加元素时的基本操作,如果数组已经满了,会进行扩容。
private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; }
2. public boolean add(E e)
这个方法是 ArrayList
类的公共 add
方法,用于将元素添加到列表的末尾。它的实现很简单,调用了上面提到的私有 add
方法来执行实际的添加操作。
modCount++
:modCount
是ArrayList
用来跟踪结构修改次数的变量。修改次数会在每次改变ArrayList
内容时增加,这主要是为了支持Iterator
的快速失败机制(fail-fast)。如果在迭代过程中修改了集合,Iterator
会抛出ConcurrentModificationException
。add(e, elementData, size)
:通过调用私有的add
方法来将元素e
添加到elementData
数组中的size
位置。
最终,add
方法返回 true
,表示元素已成功添加。
public boolean add(E e) { modCount++; add(e, elementData, size); return true; }
3. public void add(int index, E element)
这个方法是 ArrayList
的另一个 add
方法,用于在指定的位置 index
插入元素。它比单纯的添加元素复杂,因为插入元素时可能会影响到现有元素的位置。此方法的实现步骤如下:
rangeCheckForAdd(index)
:检查插入位置是否有效。rangeCheckForAdd
方法会验证index
是否在有效范围内(0 到size
)。如果超出范围,会抛出IndexOutOfBoundsException
异常。modCount++
:修改次数增加。- 计算当前
size
和elementData
数组:- 如果数组已经满了(即
s == elementData.length
),则扩容数组。
- 如果数组已经满了(即
System.arraycopy()
:这是数组元素的移动操作。System.arraycopy
方法会将index
位置及之后的元素向后移动一位,为新元素腾出空间。elementData, index, elementData, index + 1, s - index
表示将数组中从index
开始的元素移动到右边一个位置(从index + 1
开始)。
elementData[index] = element;
:将新元素element
放入指定的index
位置。- 更新
size = s + 1
:最后更新size
,表示当前元素数量增加。
4.扩容
这段代码来自于 ArrayList
的 grow
方法,用于在 ArrayList
容量不足时扩展其底层数组的容量。
private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); }
这段代码是 ArrayList
类中的一个方法,用于计算扩容时的新数组容量。它的目的是根据当前数组的容量和所需的最小容量(minCapacity
),计算一个合适的新容量,并确保容量不会过大,避免出现内存溢出问题。
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 获取当前数组的容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 计算新的容量,增加50%
// 如果新的容量小于所需的最小容量,则做进一步的处理
if (newCapacity - minCapacity <= 0) {
// 如果当前数组是空数组并且其容量是默认空数组容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity); // 返回最大值,确保容量不小于默认值
// 如果 minCapacity 小于 0,说明发生了溢出,抛出内存不足异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 否则,返回 minCapacity 确保新容量至少为所需的容量
return minCapacity;
}
// 如果新的容量小于等于最大数组大小,则直接返回新的容量
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity); // 否则调用 hugeCapacity 方法,确保容量不超过最大限制
}
定义时只初始化,添加第一个元素时,创建为10,超出时扩容为1.5倍。
5.数组与集合之间的转换