复制一个数组

407

我有一个数组 a,它经常被更新。假设 a = [1,2,3,4,5]。我需要创建一个确切的副本并将其命名为 b。如果 a 更改为 [6,7,8,9,10]b 应该仍然是 [1,2,3,4,5]。如何最好地实现这一点?我尝试了使用 for 循环:

for(int i=0; i<5; i++) {
    b[i]=a[i];
}

但是它似乎不能正常工作。请不要使用高级术语,如深复制等,因为我不知道那是什么意思。

11个回答

633
你可以尝试使用 System.arraycopy()
int[] src  = new int[]{1,2,3,4,5};
int[] dest = new int[5];

System.arraycopy( src, 0, dest, 0, src.length );

然而,在大多数情况下最好使用clone():

int[] src = ...
int[] dest = src.clone();

11
不重新发明轮子值得赞赏。据我所知,这个解决方案是在数组复制方面速度最快的。 - Felipe Hummel
9
克隆和数组复制都是本地方法。我预计克隆会稍微快一些,尽管这种差异并不重要。 - MeBigFatGuy
5
仅在大数组情况下使用 @Felipe,@MeBigFatGuy。对于小数组,由于设置开销较大,复制循环可能更快。如果您查看System.arraycopy的javadoc,您会发现该方法需要在开始之前检查各种事项。根据静态数组类型,其中一些检查对于复制循环来说是不必要的。 - Stephen C
7
@FelipeHummel,@MeBigFatGuy,@StephenC - 这是在这里的答案中提到的数组复制方法的性能测试。 在该设置中,对于250,000个元素,clone()被证明是最快的。 - Adam
23
很遗憾地看到这里所有的讨论都是关于微小的性能问题,而这在99.999%的情况下并不重要。更重要的是 src.clone() 更易读,出错机会比分配新数组并执行 arraycopy 少得多。(并且也很快) - Brian Goetz
显示剩余5条评论

246

你可以使用

int[] a = new int[]{1,2,3,4,5};
int[] b = a.clone();

同样也要。


6
我只是澄清原帖的观点:“*如果A更改为[6,7,8,9,10],B应该仍然是[1,2,3,4,5]*”。原帖作者表示尝试使用循环但未成功。 - Harry Joy
16
不需要使用cast,一个好的静态分析工具会警告它。但是克隆绝对是制作数组副本的最佳方式。 - erickson
5
@MeBigFatGuy - OP 的使用情境需要重复将内容复制到同一个数组中,因此克隆不可行。 - Stephen C
4
@Stephen C,我没有阅读那篇文章。我只看到他想要一份副本,并随后会不断更新非存储版本。 - MeBigFatGuy
4
他说:“我有一个数组A,它不断被更新。”或许我理解过多了,但我认为这意味着他也在将A重复地复制到B中。 - Stephen C
显示剩余3条评论

200

如果您想复制:

int[] a = {1,2,3,4,5};

这是正确的方式:

int[] b = Arrays.copyOf(a, a.length);

Arrays.copyOf在处理小型数组时可能会比a.clone()更快。两者都可以快速复制元素,但是clone()返回Object,所以编译器必须插入隐式转换为int[]。您可以在字节码中看到它,类似于这样:

ALOAD 1
INVOKEVIRTUAL [I.clone ()Ljava/lang/Object;
CHECKCAST [I
ASTORE 2

68

来自http://www.journaldev.com/753/how-to-copy-arrays-in-java的良好解释。

Java数组复制方法

Object.clone():Object类提供了clone()方法,由于Java中的数组也是一个对象,因此可以使用此方法实现完全复制。如果您想要部分复制数组,则此方法将不适合您。

System.arraycopy():System类的arraycopy()是执行数组部分复制的最佳方式。它为您提供了一种简单的方法来指定要复制的元素总数以及源数组和目标数组的索引位置。例如,System.arraycopy(source, 3, destination, 2, 5)将从源的第3个索引开始复制5个元素到目标的第2个索引处。

Arrays.copyOf():如果您想要复制数组的前几个元素或整个数组的副本,则可以使用此方法。显然,它不像System.arraycopy()那样通用,但也不会令人困惑并且易于使用。

Arrays.copyOfRange():如果您想要复制数组的一些元素,而起始索引不是0,则可以使用此方法来复制部分数组。


很遗憾,答案中的链接已经失效了,显示为“404 Not Found”。 - undefined

37
我有一种感觉,所有这些“更好的数组复制方式”都不会真正解决你的问题。
你说:
「我尝试了一个for循环,但好像不起作用?」。
看着那个循环,没有明显的原因使它不能工作... 除非:
  • 你以某种方式混淆了数组a和b(例如,a和b引用同一个数组),或者
  • 你的应用程序是多线程的且不同的线程同时读取和更新数组a。
在任一情况下,使用替代的复制方式都无法解决基本问题。
第一种情景的修复方法很明显。对于第二种情景,你必须找出一种同步线程的方法。原子数组类没有带有原子复制构造函数或克隆方法,但使用基元互斥体进行同步将会奏效。
(你的问题中有提示让我想到这确实涉及线程;例如, 你的陈述是a不断变化。)

18

你可以尝试在Java中使用Arrays.copyOf()

int[] a = new int[5]{1,2,3,4,5};
int[] b = Arrays.copyOf(a, a.length);

4
冗余:https://dev59.com/PG025IYBdhLWcg3wzZX3#15962949 说了同样的话。 - Stephen C

9

您还可以使用Arrays.copyOfRange方法。

示例

public static void main(String[] args) {
    int[] a = {1,2,3};
    int[] b = Arrays.copyOfRange(a, 0, a.length);
    a[0] = 5;
    System.out.println(Arrays.toString(a)); // [5,2,3]
    System.out.println(Arrays.toString(b)); // [1,2,3]
}

这个方法类似于Arrays.copyOf,但是更加灵活。它们都在底层使用System.arraycopy

参见:


答案中的最后一个链接似乎无法访问 - "ERR_CONNECTION_TIMED_OUT"。 - undefined

9
所有调用数组长度的解决方案,都要添加冗余的空值检查代码。例如:
``` if (array != null) { int length = array.length; } ```
int[] a = {1,2,3,4,5};
int[] b = Arrays.copyOf(a, a.length);
int[] c = a.clone();

//What if array a comes as local parameter? You need to use null check:

public void someMethod(int[] a) {
    if (a!=null) {
        int[] b = Arrays.copyOf(a, a.length);
        int[] c = a.clone();
    }
}

我建议您不要重复造轮子,而是使用实用程序类,其中已经执行了所有必要的检查。考虑使用apache commons中的ArrayUtils。这样您的代码会更短:

public void someMethod(int[] a) {
    int[] b = ArrayUtils.clone(a);
}

你可以在这里找到Apache commons。


4
如果您必须使用原始数组而不是ArrayList,那么Arrays就有您所需的内容。如果您查看源代码,这些绝对是获取数组副本的最佳方法。它们具有良好的防御性编程,因为如果您提供了不合逻辑的参数,System.arraycopy()方法会抛出大量未经检查的异常。
您可以使用Arrays.copyOf(),它将从第一个到第N个元素复制到新的较短数组中。
public static <T> T[] copyOf(T[] original, int newLength)

复制指定的数组,如果需要,截断或填充null以使副本具有指定的长度。对于在原始数组和副本中都有效的所有索引,两个数组将包含相同的值。对于在副本中但不在原始数组中有效的任何索引,副本将包含null。只有在指定的长度大于原始数组的长度时,才会出现这样的索引。生成的数组与原始数组完全相同。
2770
2771    public static <T,U> T[] More ...copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2772        T[] copy = ((Object)newType == (Object)Object[].class)
2773            ? (T[]) new Object[newLength]
2774            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
2775        System.arraycopy(original, 0, copy, 0,
2776                         Math.min(original.length, newLength));
2777        return copy;
2778    }

或者Arrays.copyOfRange()也可以完成这个技巧:

public static <T> T[] copyOfRange(T[] original, int from, int to)

将指定范围的指定数组复制到新数组中。该范围的初始索引(from)必须位于零和 original.length 之间,包括这两个值。原始数组中的 original[from] 值被放置在副本的初始元素中(除非 from == original.length 或 from == to)。来自原始数组中后续元素的值放置在副本中的后续元素中。范围的最终索引(to)必须大于或等于 from,可能大于 original.length,在这种情况下,null 被放置在所有索引大于或等于 original.length - from 的元素的副本中。返回数组的长度将是 to - from。生成的数组与原始数组完全相同。
3035    public static <T,U> T[] More ...copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
3036        int newLength = to - from;
3037        if (newLength < 0)
3038            throw new IllegalArgumentException(from + " > " + to);
3039        T[] copy = ((Object)newType == (Object)Object[].class)
3040            ? (T[]) new Object[newLength]
3041            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
3042        System.arraycopy(original, from, copy, 0,
3043                         Math.min(original.length - from, newLength));
3044        return copy;
3045    }

如您所见,这两个函数只是System.arraycopy的包装函数,带有防御逻辑验证您要执行的操作是否有效。

System.arraycopy 是复制数组绝对最快的方法。


3

要创建一个空值安全的数组副本,你也可以使用 Object.clone() 方法和这个答案中提供的可选项。

int[] arrayToCopy = {1, 2, 3};
int[] copiedArray = Optional.ofNullable(arrayToCopy).map(int[]::clone).orElse(null);

1
尽管这个解决方案过于复杂,但它还引入了内存浪费。如果数组包含机密信息(例如密码的字节数组),它还会引入安全漏洞,因为中间对象将驻留在堆上,直到垃圾回收,并可能暴露给攻击者。 - Weltraumschaf
1
我不同意这个构造函数中的数组会在堆上。实际上,它只在需要时调用克隆方法,而Optional对象只是一个空对象,引用现有的数组。关于性能影响,我认为现在说它实际上有影响还为时过早,因为这种类型的构造函数是JVM内联的好候选,因此与其他方法相比没有更多的影响。将其视为更复杂或不复杂是一种风格(函数式编程与过程式编程之间的区别,但不仅限于此)。 - Nicolas Henneaux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接