一、数据结构与成员变量
ArrayList
内部主要使用一个Object
类型的数组elementData
来存储元素。此外,它还有两个重要的成员变量:size
表示当前列表中实际存储的元素个数;modCount
用于记录对列表结构修改的次数,主要在迭代器中用于检测并发修改。
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private transient int modCount = 0;
二、构造方法
默认构造方法:当使用默认构造方法创建ArrayList
时,内部数组被初始化为一个空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。这意味着在首次添加元素时,会将内部数组的容量设置为默认容量 10。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
指定初始容量的构造方法:如果传入一个正整数作为初始容量参数,ArrayList
会创建一个指定大小的数组来存储元素。如果传入的初始容量为 0,则内部数组被初始化为EMPTY_ELEMENTDATA
。如果传入的初始容量小于 0,则会抛出IllegalArgumentException
异常。
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);
}
}
三、添加元素方法
add(E e)
方法:- 这个方法用于在列表末尾添加一个元素。首先,它会调用
ensureCapacityInternal(size + 1)
来确保内部数组有足够的空间容纳新元素。如果当前数组已满,就会触发扩容操作。 - 然后,将新元素赋值给内部数组的下一个空位(当前
size
位置),并将size
加一。
- 这个方法用于在列表末尾添加一个元素。首先,它会调用
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
add(int index, E element)
方法:- 此方法用于在指定位置插入一个元素。首先,通过
rangeCheckForAdd(index)
检查指定的索引是否合法。如果索引小于 0 或大于当前列表的大小,就会抛出IndexOutOfBoundsException
异常。 - 接着,调用
ensureCapacityInternal(size + 1)
确保有足够的空间。如果需要扩容,会先进行扩容操作。 - 然后,使用
System.arraycopy
方法将指定位置及之后的元素向后移动一位,为新元素腾出空间。最后,将新元素插入到指定位置,并将size
加一。
- 此方法用于在指定位置插入一个元素。首先,通过
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
四、扩容机制
ensureCapacityInternal(int minCapacity)
方法:- 这个方法首先计算内部数组需要的最小容量。如果当前内部数组为空(即
elementData
等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
),则将最小容量设置为默认容量(10)和传入的最小容量中的较大值。 - 然后,调用
ensureExplicitCapacity(minCapacity)
方法进一步处理扩容需求。
- 这个方法首先计算内部数组需要的最小容量。如果当前内部数组为空(即
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity(Object[] elementData, int minCapacity)
方法:- 如果内部数组为空,根据传入的最小容量和默认容量(10)确定新的最小容量。如果内部数组不为空,则直接返回传入的最小容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ensureExplicitCapacity(int minCapacity)
方法:- 增加
modCount
,表示对列表进行了一次结构修改。 - 如果所需的最小容量大于当前数组的长度,则调用
grow(minCapacity)
方法进行扩容。
- 增加
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow(int minCapacity)
方法:- 首先获取当前数组的旧容量
oldCapacity
。 - 计算新容量
newCapacity
,通常为旧容量的 1.5 倍(旧容量加上旧容量右移一位的值)。 - 如果新容量仍然小于所需最小容量,则将新容量设置为所需最小容量。
- 如果新容量超过了最大数组大小(
Integer.MAX_VALUE - 8
),则根据所需最小容量调用hugeCapacity(minCapacity)
方法来确定最终的新容量。 - 最后,使用
Arrays.copyOf
方法将原数组中的元素复制到新的更大的数组中,并更新内部数组的引用。
- 首先获取当前数组的旧容量
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);
}
hugeCapacity(int minCapacity)
方法:- 如果所需最小容量大于最大数组大小,则抛出
OutOfMemoryError
异常。否则,返回Integer.MAX_VALUE
和所需最小容量中的较小值与最大数组大小中的较小值。
- 如果所需最小容量大于最大数组大小,则抛出
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
五、获取元素方法
get(int index)
方法:- 这个方法用于获取指定索引处的元素。首先,通过
rangeCheck(index)
检查索引是否合法。如果索引小于 0 或大于等于当前列表的大小,就会抛出IndexOutOfBoundsException
异常。 - 然后,直接返回内部数组中对应索引处的元素。
- 这个方法用于获取指定索引处的元素。首先,通过
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
六、删除元素方法
remove(int index)
方法:- 此方法用于删除指定索引处的元素。首先,通过
rangeCheck(index)
检查索引是否合法。 - 然后,记录要删除的元素的值
oldValue
。 - 计算需要移动的元素个数
numMoved
,即列表中指定索引之后的元素个数。 - 如果有元素需要移动(即
numMoved
大于 0),则使用System.arraycopy
方法将指定索引之后的元素向前移动一位。 - 最后,将列表的大小减一,并将最后一个位置的元素设置为
null
,以便垃圾回收器可以回收该元素。同时,返回被删除的元素。
- 此方法用于删除指定索引处的元素。首先,通过
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);
elementData[--size] = null;
return oldValue;
}
remove(Object o)
方法:- 这个方法用于删除指定的元素。如果元素为
null
,则遍历列表找到第一个为null
的元素并删除;如果元素不为null
,则遍历列表找到第一个与给定元素相等(通过equals
方法判断)的元素并删除。 - 如果没有找到要删除的元素,则返回
false
。
- 这个方法用于删除指定的元素。如果元素为
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
fastRemove(int index)
方法是一个内部辅助方法,用于快速删除指定索引处的元素,其实现与remove(int index)
方法类似,但不返回被删除的元素。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null;
}
七、遍历方式
- 使用迭代器遍历:
ArrayList
实现了Iterable
接口,因此可以使用迭代器进行遍历。迭代器提供了一种统一的方式来遍历集合,并且可以在遍历过程中安全地删除元素。- 通过调用
list.iterator()
可以获取一个Iterator
对象,然后使用hasNext()
和next()
方法来遍历列表中的元素。
Iterator<E> iterator = list.iterator();
while (iterator.hasNext()) {
E element = iterator.next();
// 对元素进行操作
}
- 使用增强型
for
循环遍历:- 增强型
for
循环(也称为“foreach”循环)提供了一种简洁的方式来遍历集合。在内部,它使用迭代器来遍历集合中的元素。 - 语法为
for (E element : list)
,其中element
是每次迭代的当前元素,list
是要遍历的ArrayList
对象。
- 增强型
for (E element : list) {
// 对元素进行操作
}
总之,ArrayList
底层通过数组实现,具有高效的随机访问性能,但在插入和删除元素时可能需要移动大量元素,效率相对较低。其扩容机制确保了可以动态地存储任意数量的元素,同时提供了多种添加、删除、获取和遍历元素的方法,方便开发者在不同场景下使用。