有啥理由更喜欢 System.arraycopy() 而不是 clone()?

Posted

技术标签:

【中文标题】有啥理由更喜欢 System.arraycopy() 而不是 clone()?【英文标题】:Is there any reason to prefer System.arraycopy() over clone()?有什么理由更喜欢 System.arraycopy() 而不是 clone()? 【发布时间】:2011-11-03 00:03:08 【问题描述】:

在复制整个数组时,我经常看到人们这样写:

int[] dest = new int[orig.length];
System.arraycopy(orig, 0, dest, 0, orig.length);

但在我看来,没有理由赞成这一点:

int[] dest = orig.clone();

无论如何,它们都是浅拷贝。可能这些人只是没有意识到clone 的存在。那么有什么理由不使用clone

【问题讨论】:

System.arraycopy 将每个元素从orig 复制到dest,然后是深拷贝,而不是浅拷贝。 它使dest[0] 引用与orig[0] 相同的对象。所以如果orig[0] 是一个数组,dest[0] 将包含完全相同的数组实例;它不会克隆子阵列。这不是深拷贝。这不正确吗? 在您的示例中,您使用了int 原语。然后dest[0] 得到orig[0] 在复制时的值。如果之后orig[0] 发生变化,则不会再影响dest[0]。如果该值是一个对象引用(就像在 Object[] 数组中一样(其元素可以是 int[] 数组),那么该对象当然可能会发生变异。 当然,但我的例子只是一个例子。关键是arraycopy 并不比clone“更深”,无论你对深/浅的含义是什么。 【参考方案1】:

使用System.arraycopyArrays.copyOf 代替clone() 会使您的代码更明确,因此更易于理解。它说 “我正在复制一个数组”,而不是 “我正在(神奇地)复制某种对象”

显式优于隐式。 - The Zen of Python

但支持Arrays.copyOf 方法(和反对clone())的主要论据是类型安全,缺少它可能是clone() 可能导致的最大问题如果您不注意要克隆的对象具有您想要的确切数组组件类型,则会出现细微的错误。

让我们以JDK-6260652 漏洞为例。罪魁祸首是clone()Arrays.ArrayList 中用于实现Collection.toArray()(声明为返回Object[])。这个特定的ArrayListjava.util.Arrays 中的一个私有类,由Arrays.asList(T... a) 实例化,使用a 作为其支持数组,而不关心a 的实际类型,它可能是String[]Integer[](或其他实际上不是Object[])。它的toArray() 方法在这里返回a.clone() 的问题是程序员最终可能会在某些时候使用Arrays.ArrayList.toArray() 来做这样的事情:

List<String> lst = Arrays.asList("a", "b", "c");
// ... many lines of code later ...
Object[] arr = lst.toArray();
// ... at this point don't care about the original type of elements in lst ...
arr[0] = 123;  // ArrayStoreException - WTF?!
// arr is an Object[] so should be able to store Integer values!

由于上述使用模式并不常见,这种错误可能会被忽视多年。只需考虑自 JDK 1.2(1998 年)以来 Collections 框架就已经存在,但直到 2005 年才报告这个特定问题(10 年后有人在 JDK 的不同部分发现了similar issue)。 patch for JDK-6260652(在 Java 9 中发布)只是将 a.clone() 替换为 Arrays.copyOf(a, a.length, Object[].class)


总而言之,我赞成使用数组复制方法而不是 clone() 的论点是:

System.arraycopy:
    更明确
Arrays.copyOf:
    更明确 并提供类型安全

【讨论】:

性能怎么样?我刚刚读到clone()System.arraycopy() 快得多,但我正在寻找佐证【参考方案2】:

我在思考同样的疑问时碰巧看到了这个问题。我觉得arraycopy() 是一种在预定义数组时使用的方法(即已经分配了内存)。因此,不会重复与内存分配相关的开销。

例如,假设您定义了一个定期更新的大型数组。然后使用clone() 将在每次复制数组时重新创建所需大小的数组。但是,arraycopy() 使用的是预先分配的内存空间。

因此arraycopy() 在某些情况下比clone() 更有效。另一方面,clone() 会产生紧凑的代码。

【讨论】:

【参考方案3】:

否。如果您真的进行微基准测试,那么可能取决于您正在运行的 JVM。但实际上,没有。

【讨论】:

【参考方案4】: clone() 使用自己的引用创建第一个数组的不同副本。 System.arraycopy()使用JNI(Java Native Interface)复制 一个数组(或它的一部分),所以它是 如您所见,速度极快 here; clone() 创建一个 new 数组,该数组具有与旧数组相同的特征,即相同的大小、相同的类型和 相同的 内容。有关clone 的一些实际示例,请参阅here; manual copying 是手动复制。这个方法没什么好说的,只是很多人发现它是最多的performant。 arraynew = arrayold 复制数组;它只是指向 arraynewarrayold 的内存地址,或者换句话说,您只是将 reference 分配给旧数组。

【讨论】:

想解释一下arraycopy() 注释吗? 你没有回答我的问题。另外,我希望clonearraycopy 一样快。不也是原生的吗? @Neil:克隆速度较慢。见第一个链接。 查看 System.arraycopy() 文章中的代码,没有“热身”时间,所以不确定这是否是一个公平的微基准测试 @Code Monkey:来自文章本身:“使用 clone() 复制数组的代码更少,正如我们所见,性能差异仅对微型数组有意义。我认为将来我宁愿使用 clone() 而不是 System.arrayCopy()。"【参考方案5】:

这里只是猜测,但使用System.arraycopy 可能有充分的理由,因为可以想象不同的JVM 可以通过利用底层系统的本机能力来提高性能的方式来实现它们。

例如,JVM 实现可以使用像 memcpy 这样的本机库调用,它可能会利用一些内存控制器技巧以某种令人难以置信的快速和聪明的方式执行操作。但是,Object.clone 实现可能不是此类优化的理想选择,因为它具有虚拟性质。

【讨论】:

arrayCopy 使用 JNI 进行复制;真的很快。 嗯,他们正在进行基准测试的特定 JVM 实现显然可以,但我认为您不能断然地说 所有 JVM 都这样做。 我对此有点困惑。 JVM同时实现了arraycopy和clone,对吧?那么为什么实现不相似呢? “它的虚拟本质”是什么意思? @Neil Traft:我猜 JVM 将 arraycopy 实现为本机方法,但 Object.clone 是在 Java 语言本身中实现的(作为 standard class library 的一部分)。通过“虚拟性质”,我的意思是根 Object 类实现有一个“克隆”方法,但子类用自己的实现覆盖它们,所以也许这种“虚拟方法调度”使执行我想象的优化技巧的能力复杂化. @maerics:但是......当我查看来自 Sun JDK 发行版的 Object.java 的源代码时,它显示“受保护的 native Object clone()”。

以上是关于有啥理由更喜欢 System.arraycopy() 而不是 clone()?的主要内容,如果未能解决你的问题,请参考以下文章

在 Perl 中有啥理由更喜欢 glob 而不是 readdir(反之亦然)?

使用 System.arraycopy(...) 比使用 for 循环复制数组更好吗?

System.arraycopy 和 Array.clone 的比较

System.arraycopy 不抛出 ArrayIndexOutOfBoundsException

是否有任何理由更喜欢数据挖掘项目的函数式编程? [关闭]

你会喜欢Flutter的5个理由