目录

一、先看源码

        1、ArrayList.java源码

        2、System.java源码

二、先搞懂 System.arraycopy 的语法

        1、它是 Java 原生的数组拷贝方法,参数顺序是:

        2、对应ArrayList.java源代码里的参数

三、它到底做了什么?

四、为什么要这么做?

五、补充说明

        1、elementData 既是源数组又是目标数组,这是原地拷贝,不会额外占用内存。

        2、拷贝的长度 s - index,是从 index 到数组有效元素末尾的所有元素个数,保证移动后不会丢失任何数据。

六、完整示例:模拟 ArrayList 插入过程

        1、代码实例

        2、运行结果

七、过程拆解

        1、插入前:

        2、arraycopy 执行后:

        3、放入新元素:

八、关键结论

        1、System.arraycopy 做的事,就是把 index 及其后面的元素整体向后移动一位,为新元素腾出位置。

        2、这种原地拷贝的方式,保证了数组在插入时不会丢失数据,但也导致了 ArrayList 在中间位置插入的时间复杂度是 O(n)(需要移动 n 个元素)。


一、先看源码

1、ArrayList.java源码

2、System.java源码

二、先搞懂 System.arraycopy 的语法

1、它是 Java 原生的数组拷贝方法,参数顺序是:
System.arraycopy(
    Object src,       // 源数组
    int srcPos,       // 源数组中开始拷贝的位置
    Object dest,      // 目标数组
    int destPos,      // 目标数组中开始写入的位置
    int length        // 要拷贝的元素个数
);
2、对应ArrayList.java源代码里的参数
System.arraycopy(
    elementData,      // 源数组:就是 ArrayList 底层的存储数组
    index,            // 源起始位置:从 index 位置开始拷贝
    elementData,      // 目标数组:还是同一个数组(原地拷贝)
    index + 1,        // 目标起始位置:从 index+1 位置开始写入
    s - index         // 拷贝长度:从 index 到末尾,共有 s - index 个元素
);

三、它到底做了什么?

举个例子:

假设当前数组 elementData 里有 [A, B, C, D]size = 4,你要在 index=1 的位置插入元素。

执行 System.arraycopy 时:

  • 1、从 index=1 开始,拷贝 4 - 1 = 3 个元素(也就是 B, C, D
  • 2、把它们写入到 index+1=2 的位置
  • 3、执行后数组变成:[A, B, B, C, D]

然后下一行代码 elementData[index] = element; 会把 index=1 的位置替换成新元素,比如 X,最终数组就是:[A, X, B, C, D]

四、为什么要这么做?

因为数组是连续内存结构,不能直接在中间插入元素。要在 index 位置插入新元素,必须先把 index 及其后面的所有元素整体向后移动一位,腾出 index 位置,再放入新元素。

  • 这就是 ArrayList 插入效率低的根本原因:移动元素需要 O (n) 时间,插入位置越靠前,需要移动的元素越多。

五、补充说明

1、elementData 既是源数组又是目标数组,这是原地拷贝,不会额外占用内存。
2、拷贝的长度 s - index,是从 index 到数组有效元素末尾的所有元素个数,保证移动后不会丢失任何数据。

六、完整示例:模拟 ArrayList 插入过程

1、代码实例
public class ArrayListInsertDemo {
    public static void main(String[] args) {
        // 模拟 ArrayList 底层数组
        Object[] elementData = {"A", "B", "C", "D", null, null};
        // 当前有效元素个数(size)
        int s = 4;
        // 要插入的位置
        int index = 1;
        // 要插入的元素
        String element = "X";

        System.out.println("插入前数组状态:");
        printArray(elementData, s);

        // 执行 System.arraycopy,把 index 及其后面的元素整体后移一位
        System.arraycopy(
            elementData,      // 源数组
            index,            // 源起始位置:从 index=1 开始拷贝
            elementData,      // 目标数组(原地拷贝)
            index + 1,        // 目标起始位置:从 index+1=2 开始写入
            s - index         // 拷贝长度:4 - 1 = 3 个元素(B、C、D)
        );

        System.out.println("\n执行 arraycopy后(元素已后移,index位置空出来了):");
        printArray(elementData, s);

        // 把新元素放入 index 位置
        elementData[index] = element;
        // 有效元素个数 +1
        s = s + 1;

        System.out.println("\n插入完成后的数组状态:");
        printArray(elementData, s);
    }

    // 辅助方法:打印数组有效元素
    private static void printArray(Object[] array, int size) {
        for (int i = 0; i < size; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}
2、运行结果
插入前数组状态:
A B C D 

执行 arraycopy 后(元素已后移,index 位置空出来了):
A B B C D 

插入完成后的数组状态:
A X B C D 

七、过程拆解

1、插入前

                数组有效元素是 [A, B, C, D],要在 index=1 插入 X

2、arraycopy 执行后

      B、C、D 整体后移一位,数组变成 [A, B, B, C, D]index=1 位置被空出来(此时还是原来的 B,但马上会被覆盖)。

3、放入新元素

                把 index=1 位置替换为 X,最终得到 [A, X, B, C, D]

八、关键结论

1、System.arraycopy 做的事,就是把 index 及其后面的元素整体向后移动一位,为新元素腾出位置。
2、这种原地拷贝的方式,保证了数组在插入时不会丢失数据,但也导致了 ArrayList 在中间位置插入的时间复杂度是 O(n)(需要移动 n 个元素)。

更多推荐