
👨💻程序员三明治:个人主页 🔥 个人专栏: 《设计模式精解》 《重学数据结构》
🤞先做到 再看见!

ArrayList其实就是List接口下的一个重要实现,也就是一个动态数组,当更多的元素加入到ArrayList中时,其大小将会动态地增长,内部的元素可以直接通过get与set方法进行访问。
流程图如下:

下面我们来自己手撕一个ArrayList,来看看它的底层是怎么样的。
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
public class ArrayListCapacityDemo {
// 自定义ArrayList类用于演示扩容过程
static class DebuggableArrayList<E> {
// 实际存储元素的数组
private Object[] elementData;
// 当前元素数量
private int size;
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 共享的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
public DebuggableArrayList() {
this.elementData = EMPTY_ELEMENTDATA;
this.size = 0;
}
// 添加元素方法
public boolean add(E e) {
// 确保容量足够
ensureCapacityInternal(size + 1);
// 添加元素
elementData[size++] = e;
return true;
}
// 内部容量检查
private void ensureCapacityInternal(int minCapacity) {
// 计算所需容量
int capacity = calculateCapacity(elementData, minCapacity);
// 明确是否需要扩容
ensureExplicitCapacity(capacity);
}
// 容量计算
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是空数组,返回默认容量和所需容量的较大值
if (elementData == EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 明确是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
// 修改计数器(用于迭代器快速失败机制)
// modCount++;
// 如果所需容量大于当前数组长度,则扩容
if (minCapacity - elementData.length > 0) {
grow(minCapacity);
}
}
// 最大数组大小(一些VM在数组中保留头信息)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容核心方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + 旧容量/2 (即1.5倍增长)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果1.5倍仍不够,则使用minCapacity
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 如果超过最大限制,则调整
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
// 打印扩容信息(调试用)
System.out.printf("扩容: %d -> %d (添加第%d个元素时触发)%n",
oldCapacity, newCapacity, size + 1);
// 实际扩容操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 处理超大容量
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) { // 溢出
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
// 获取当前容量(仅用于演示)
public int capacity() {
return elementData.length;
}
}
public static void main(String[] args) {
System.out.println("ArrayList扩容演示:");
System.out.println("----------------");
DebuggableArrayList<Integer> list = new DebuggableArrayList<>();
// 添加30个元素,观察扩容过程
for (int i = 0; i < 30; i++) {
list.add(i);
System.out.printf("添加元素: %2d, 当前大小: %2d, 当前容量: %2d%n",
i, i + 1, list.capacity());
}
}
}整体流程概览:
add(E e) 方法调用: 用户调用 add 方法尝试添加一个新元素。ensureCapacityInternal): add 方法首先会调用 ensureCapacityInternal 来确保内部数组有足够的容量。calculateCapacity): 如果是第一次添加元素(即内部数组是 EMPTY_ELEMENTDATA),则计算出初始容量(通常是 DEFAULT_CAPACITY 即 10 或用户指定的 minCapacity 中的较大值)。ensureExplicitCapacity): 这一步会判断当前数组的实际长度是否小于所需的最小容量。grow): 如果 minCapacity 大于 elementData.length,则会调用 grow 方法执行实际的扩容操作。grow 方法会根据当前容量计算一个新的容量值(通常是旧容量的 1.5 倍)。minCapacity,或者新容量超过了 MAX_ARRAY_SIZE,则会进行相应的调整。Arrays.copyOf 将旧数组的元素复制到一个新的、更大容量的数组中。elementData 数组中,size 增加。

因为数组是在创建的时候就指定好大小的,不可变。所以说他需要new一个新的数组然后拷贝过去,然后旧的数组会被gc标记为一个没引用的就会被回收掉
size 和 capacity 的区别size:ArrayList 中实际存在的元素数量。这是 list.size() 返回的值。capacity:内部数组在不进行扩容的情况下可以容纳的最大元素数量。这是 elementData.length。size 总是小于或等于 capacity。当 size 等于 capacity 时,添加新元素就会触发扩容。add(E e) (摊销常数时间 O(1)):在列表末尾添加元素。大多数情况下是 O(1)。只有当发生扩容时,由于所有元素需要被复制,它会变为 O(N)。然而,由于扩容不常发生且以固定倍数(1.5 倍)增长,平均(摊销)成本仍然是 O(1)。get(int index) (常数时间 O(1)):通过索引访问元素非常快,因为它是一个直接的数组查找。set(int index, E element) (常数时间 O(1)):替换特定索引处的元素也是 O(1)。add(int index, E element) (线性时间 O(N)):在任意位置(非末尾)插入元素。从该索引开始到末尾的所有元素都必须向右移动一个位置。在最坏情况下(插入到索引 0),所有 N 个元素都需要移动。remove(int index) (线性时间 O(N)):删除任意位置的元素。从 index + 1 到末尾的所有元素都必须向左移动一个位置。在最坏情况下(删除索引 0),所有 N-1 个元素都需要移动。remove(Object o) (线性时间 O(N)):查找并删除一个对象。这首先需要遍历列表找到该对象,然后移动元素。contains(Object o) (线性时间 O(N)):查找元素。如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!