Java中ArrayList是集合框架里常用的动态数组实现,而手动扩容数组则是开发者自己通过数组拷贝实现容量动态调整的方案,两者看似功能相似,实际在设计和性能上有诸多不同。

一、本质区别分析
1. 底层实现逻辑
ArrayList是Java标准库提供的成熟实现,底层基于Object数组存储元素,封装了所有扩容、元素操作的逻辑,对外提供统一的add、get、remove等接口。而手动扩容数组需要开发者自己维护一个普通数组,当容量不足时手动创建新数组并拷贝原有元素。
以下是手动扩容数组的基础实现示例:
public class ManualResizeArray<E> {
// 底层存储数组
private Object[] elementData;
// 当前元素数量
private int size;
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
public ManualResizeArray() {
elementData = new Object[DEFAULT_CAPACITY];
size = 0;
}
// 添加元素
public void add(E e) {
// 容量不足时手动扩容
if (size == elementData.length) {
resize();
}
elementData[size++] = e;
}
// 手动扩容逻辑,容量翻倍
private void resize() {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity * 2;
Object[] newArray = new Object[newCapacity];
// 拷贝原有元素
System.arraycopy(elementData, 0, newArray, 0, oldCapacity);
elementData = newArray;
}
// 获取元素
public E get(int index) {
checkIndex(index);
return (E) elementData[index];
}
// 索引校验
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引超出范围");
}
}
}
2. 扩容策略差异
ArrayList的扩容策略并非固定翻倍,JDK1.8中默认初始容量为10,当容量不足时,新容量为oldCapacity + (oldCapacity >> 1),也就是原容量的1.5倍,同时提供了ensureCapacity方法可以提前指定扩容目标,减少扩容次数。
而手动扩容数组的扩容策略完全由开发者定义,常见的有翻倍扩容、固定增量扩容等,策略的合理性直接影响性能,如果策略设计不当,可能出现频繁扩容或者内存浪费的问题。
3. 类型安全与功能完整性
ArrayList支持泛型,编译期就能进行类型校验,避免运行时类型转换错误,同时内置了迭代器、序列化、批量操作等完整功能。手动扩容数组如果要做泛型支持需要额外处理,且大部分功能都需要开发者自己实现,容易出现逻辑漏洞。
二、性能影响对比
1. 扩容开销
两者的扩容都需要创建新数组和拷贝元素,核心开销一致,但ArrayList的扩容逻辑经过长期优化,效率更稳定。如果手动扩容数组的扩容策略不合理,比如每次只扩容1个容量,会导致频繁扩容,性能远差于ArrayList。
2. 读写效率
随机访问场景下,两者都是基于数组下标访问,时间复杂度都是O(1),效率几乎没有差异。但在尾部添加元素的场景下,ArrayList如果提前通过ensureCapacity预分配足够容量,可以避免扩容开销,和手动扩容数组的最优表现一致。
3. 内存占用
ArrayList除了底层数组外,还会存储size、序列化ID等额外字段,内存占用略高于手动扩容数组,但差距极小。如果手动扩容数组的扩容策略不合理,比如频繁扩容导致大量空闲容量,反而会比ArrayList占用更多内存。
三、场景选型建议
- 如果是常规业务开发,优先选择ArrayList,减少重复造轮子,降低出错概率。
- 如果对内存占用有极端要求,或者需要定制特殊的扩容策略,可以考虑手动实现扩容数组。
- 如果已知元素大致数量,使用ArrayList时提前调用
ensureCapacity方法,可以获得更好的性能表现。