ArrayList是一个基于数组实现的动态数组结构。与普通 Java 数组不同,普通数组在创建时需指定长度且长度固定,而 ArrayList 的容量可根据元素的添加或删除动态变化,可看作是能自动调整大小的数组 。下面我们来解读一下它的源码,体会当初创作者更深的思考。
1.构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这代表创建ArrayList时会自动调用无参构造,
elementData:ArrayList的核心成员变量,是一个Object[]数组,用于存储实际元素。DEFAULTCAPACITY_EMPTY_ELEMENTDATA:ArrayList类中的静态常量,定义为一个空数组{}。其作用是标记该ArrayList在首次添加元素时需要扩容至默认容量。
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);
}
}
有参构造,非常好懂。
2.add方法,增加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这是add的方法的实现,但是我们只能明白它对 elementData[size++] = e;进行赋值,但是我们并没有看到扩容操作。那么就是与 ensureCapacityInternal(size + 1); 有关。我们点进来
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
这里的 点进来发现是常量DEFAULT_CAPACITY10
private static final int DEFAULT_CAPACITY = 10;
就是如果本身为空,就比较期望与10,如果无参构造,就是1和10比较,自然选10,如果是有参构造,就选较大值。用这句ensureExplicitCapacity(minCapacity);扩容。
点进来看
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount是ArrayList的父类AbstractList中的一个字段,记录集合结构被修改的次数。这里比较minCapacity ,elementData.length,就是如果要求最小长度大于elementData.length数组长度,就扩容 grow(minCapacity);在点进来
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);这两句表明新长度先扩容原数组长度的1.5倍,下面第一个if表示如还够大,就直接newCapacity = minCapacity;
MAX_ARRAY_SIZE是ArrayList允许的最大容量,通常为Integer.MAX_VALUE - 8(避免某些 VM 的内存溢出)。- 若新容量超过该值,则调用
hugeCapacity方法处理极端情况private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } - 若
minCapacity为负数(溢出),抛出OutOfMemoryError。 - 否则,返回
Integer.MAX_VALUE或MAX_ARRAY_SIZE(取决于minCapacity是否超过MAX_ARRAY_SIZE)。
正常来说还是走elementData = Arrays.copyOf(elementData, newCapacity);这句。使用Arrays.copyOf创建一个新的、更大的数组,并将原数组元素复制到新数组中。
3.删除
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; // clear to let GC do its work
return oldValue;
}
调用rangeCheck(index)确保索引在有效范围内(0 ≤ index < size)。若索引越界,抛出IndexOutOfBoundsException。modCount记录集合结构修改次数。通过elementData(index)获取指定索引位置的元素,后续作为返回值。numMoved表示需要移动的元素数量。例如,删除索引3的元素后,索引4到size-1的元素需向前移动一位。用arraycopy高效地将后续元素前移,覆盖被删除的元素。将原最后一个元素置为null,帮助垃圾回收(避免内存泄漏)。--size将列表大小减 1。
System.arraycopy方法高效的原因主要有以下几点:
- 底层实现优化:
arraycopy是一个本地方法,由 Java 虚拟机(JVM)在底层使用 C 或 C++ 等语言实现。这种底层实现能够直接操作内存,避免了 Java 语言层面的一些开销,如方法调用的栈操作、边界检查等,从而提高了复制效率。 - 批量复制:它可以一次性将一个数组中的一段元素复制到另一个数组中,而不需要像在 Java 中使用循环逐个复制元素那样,每次复制都需要进行循环条件判断、数组索引计算等操作。
arraycopy通过一次操作就可以完成大量元素的复制,减少了操作的次数,提高了效率。 - 内存连续性:数组在内存中是连续存储的,
arraycopy利用了这一特性,可以通过内存指针的移动和批量数据传输来实现高效复制。它能够利用现代计算机硬件的内存访问优化机制,如缓存行预取等,提高内存访问效率,进一步加快复制速度。
上面我们是删索引,那如果是上元素的值会怎么样?就是如果你要删的是值怎么办?

加一个Object即可。
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;
}
由于null元素只能通过==比较,非null元素需使用equals确保逻辑相等性。这里我们分开比较,下面我们点进来fastRemove(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; // clear to let GC do its work
}
和上面删除索引几乎一模一样。注意这里只删除第一个。
4.改
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
- 调用
rangeCheck(index)确保索引在有效范围内(0 ≤ index < size)。 - 若索引越界,抛出
IndexOutOfBoundsException。
剩下的就是替换,非常简单。
5.查
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
也很简单,就是要注意是否越界。点进elememtData
E elementData(int index) {
return (E) elementData[index];
}
也很简单 ,这里要注意的是无论泛型类型E是什么(如String、Integer),数组的实际类型始终是Object[]。因此,从数组中获取元素时,需要将Object强制转换为泛型类型E。
总结:我们发现源码确实很精妙和细节,我们有时有疑问可以考虑读源码来解决。


被折叠的 条评论
为什么被折叠?



