1. 概述

本文将简要分析 Java 数组与标准 ArrayList 在内存分配上的异同,并重点讲解如何向数组和 ArrayList 中追加或插入元素。

对于有经验的开发者来说,这属于基础但高频踩坑的场景 —— 特别是在性能敏感或频繁操作集合的场景下,选择不当会带来不必要的对象创建和内存拷贝开销。

2. Java 数组与 ArrayList

Java 数组是语言层面提供的基础数据结构,而 ArrayList 是 Java 集合框架中 List 接口的一个基于数组的实现类。

两者虽然底层都依赖数组存储数据,但在使用方式、灵活性和性能表现上存在显著差异。

2.1 访问与修改元素

数组使用方括号语法直接访问或修改元素,简单粗暴:

System.out.println(anArray[1]);
anArray[1] = 4;

ArrayList 提供了封装好的方法:

int n = anArrayList.get(1);
anArrayList.set(1, 4);

✅ 优势:ArrayList 方法调用更安全(边界检查),代码可读性更强
❌ 劣势:多了方法调用开销,对性能极致要求的场景可能成为瓶颈

2.2 固定大小 vs 动态扩容

这是两者最核心的区别:

  • 数组:固定大小,创建时必须指定长度,之后无法扩容
  • ArrayList:动态扩容,内部数组满时自动创建更大的数组并复制元素

ArrayList 中元素数量超过其内部数组容量时,会触发扩容机制。具体策略由其实现决定(通常是 1.5 倍增长),但本质是:

  1. 创建一个新的更大数组
  2. 将旧数组所有元素复制过去
  3. 释放旧数组引用

⚠️ 虽然单次 add 操作平均时间复杂度为 **O(1)**(摊还分析),但个别扩容操作是 O(n) 的代价。频繁添加元素时建议预设初始容量以避免反复扩容。

2.3 元素类型支持

  • 数组:支持原始类型(如 int[])和引用类型(如 String[]
  • ArrayList:只能存放引用类型,不支持原始类型

这意味着当你写:

ArrayList<Integer> list = new ArrayList<>();
list.add(42); // 自动装箱:int → Integer

Java 编译器会自动进行自动装箱(autoboxing),将 int 转为 Integer 对象。这在高频操作中可能带来额外的 GC 压力。

⚠️ 小心踩坑:频繁使用 ArrayList<Integer> 处理大量数值时,考虑用 TIntArrayList(来自 Trove 等第三方库)替代,避免装箱开销。


3. 追加元素

数组:手动扩容 + 复制

由于数组大小固定,追加元素需手动完成三步:

  1. 创建新数组(原长度 +1)
  2. 复制原数组内容
  3. 在末尾添加新元素

纯 Java 实现如下:

public Integer[] addElementUsingPureJava(Integer[] srcArray, int elementToAdd) {
    Integer[] destArray = new Integer[srcArray.length + 1];

    for (int i = 0; i < srcArray.length; i++) {
        destArray[i] = srcArray[i];
    }

    destArray[destArray.length - 1] = elementToAdd;
    return destArray;
}

更简洁的方式是使用 Arrays.copyOf()

int[] destArray = Arrays.copyOf(srcArray, srcArray.length + 1);
destArray[destArray.length - 1] = elementToAdd;

✅ 推荐:Arrays.copyOf() 底层调用 System.arraycopy(),效率更高

ArrayList:一行搞定

anArrayList.add(newElement);

✅ 简单到没朋友,内部自动处理扩容逻辑
⚠️ 注意:如果知道最终元素数量,建议构造时指定初始容量,避免多次扩容


4. 在指定索引插入元素

数组:手动搬移 + 插入

在数组中插入元素需:

  1. 创建更大数组
  2. 遍历并判断位置,插入点前正常复制,插入点后整体右移一位

示例代码:

public static int[] insertAnElementAtAGivenIndex(final int[] srcArray, int index, int newElement) {
    int[] destArray = new int[srcArray.length + 1];
    int j = 0;
    for (int i = 0; i < destArray.length; i++) {
        if (i == index) {
            destArray[i] = newElement;
        } else {
            destArray[i] = srcArray[j];
            j++;
        }
    }
    return destArray;
}

测试验证:

int[] expectedArray = { 1, 2, 42, 3, 4 };
int[] anArray = { 1, 2, 3, 4 };
int[] outputArray = ArrayOperations.insertAnElementAtAGivenIndex(anArray, 2, 42);

assertThat(outputArray).containsExactly(expectedArray);

使用 Apache Commons Lang 简化操作

ArrayUtils.insert() 方法让这事变得轻松:

int[] destArray = ArrayUtils.insert(2, srcArray, 77);

参数说明:

  • 第一个参数:插入位置(index)
  • 第二个参数:原数组
  • 第三个及以后:要插入的值(支持可变参数)

例如插入多个元素:

int[] destArray = ArrayUtils.insert(2, srcArray, 77, 88, 99);

结果:原数组从 index=2 开始的元素全部右移 3 位。

ArrayList:原生支持

anArrayList.add(index, newElement);

内部自动完成元素右移,开发者无感知。

✅ 推荐场景:需要频繁在中间插入元素时,优先选 ArrayList 而非数组


5. 在开头插入元素(Prepend)

即在索引 0 处插入元素。

数组实现方式一:复用插入逻辑

直接调用前面写的插入方法:

int[] result = ArrayOperations.insertAnElementAtAGivenIndex(anArray, 0, 42);

方式二:使用 System.arraycopy

更高效,避免手动循环:

public static int[] prependAnElementToArray(int[] srcArray, int element) {
    int[] newArray = new int[srcArray.length + 1];
    newArray[0] = element;
    System.arraycopy(srcArray, 0, newArray, 1, srcArray.length);
    return newArray;
}

System.arraycopy() 是 JVM 内部优化过的本地方法,性能优于手动 for 循环

方式三:Apache Commons Lang 快捷方法

int[] result = ArrayUtils.addFirst(anArray, 42);

底层其实还是调用 ArrayUtils.insert(0, array, element),但语义更清晰。

ArrayList 对应操作

anArrayList.add(0, newElement);

⚠️ 注意:在 ArrayList 头部插入效率较低(O(n)),因为所有后续元素都要右移。若频繁在头部操作,建议改用 LinkedList


6. 总结

特性 数组 ArrayList
✅ 大小 固定 动态扩容
✅ 元素类型 支持原始类型 仅引用类型(自动装箱)
✅ 追加元素 手动扩容 + 复制 add() 一行解决
✅ 中间插入 复杂,需手动搬移 add(index, elem) 原生支持
✅ 性能 高效,无额外开销 存在对象封装、扩容成本
✅ 使用场景 固定大小、高性能数值处理 动态集合、频繁增删

✅ 最佳实践建议:

  • 如果集合大小已知且不变 → 用数组
  • 如果需要频繁添加/删除元素 → 用 ArrayList
  • 避免在 ArrayList 头部频繁插入 → 考虑 LinkedList
  • 大量原始类型操作 → 考虑 TroveFastUtil 等第三方库减少装箱开销
  • 使用 Arrays.copyOf()System.arraycopy() 优化数组复制
  • 使用 Apache Commons Lang 的 ArrayUtils 简化常见操作

示例完整源码已托管至 GitHub:https://github.com/baeldung/core-java-modules/tree/core-java-arrays-operations-basic


原始标题:Adding an Element to a Java Array vs an ArrayList | Baeldung