【问题标题】:.clone() or Arrays.copyOf()?.clone() 还是 Arrays.copyOf()?
【发布时间】:2012-08-22 20:17:18
【问题描述】:

为了减少可变性,我们是否应该使用

public void setValues(String[] newVals) {

     this.vals = ( newVals == null ? null : newVals.clone() );
}

public void setValues(String[] newVals) {

     this.vals = ( newVals == null ? null : Arrays.copyOf(newVals, newVals.length) );
}

【问题讨论】:

  • 除非valsObject vals,否则上述内容不会编译。否则,这些是可比较的方法。 clone 是一个更简单的调用(除了丑陋的沮丧)。
  • System.arrayCopy() 怎么样(这将比您的 2 个选项更快)?
  • @assylias 你确定吗? clone 至少应该一样快,它会逐字节复制实例数据。

标签: java immutability


【解决方案1】:

就可变性而言,它们将提供完全相同的数据——浅拷贝。

【讨论】:

  • 不可变对象的浅拷贝是一种完全有效的方法。
  • @BGR copyOf 使用 System.arraycopy,我猜克隆也使用它。所以它们应该有相同的性能(不过,copyOf 对类型有一些额外的检查,所以它可能会稍微慢一些)
  • copyOf 仅在您指定结果数组类型时才需要类型检查。 System.arraycopy 类似。
【解决方案2】:

使用 jmh 更新

Using jmh,我得到了类似的结果,除了 clone 似乎稍微好一点。

原帖

我对性能进行了快速测试:cloneSystem.arrayCopyArrays.copyOf 的性能非常相似(jdk 1.7.06,服务器 vm)。

JIT之后的详细信息(以毫秒为单位):

克隆:68
数组复制:68
Arrays.copyOf: 68

测试代码:

public static void main(String[] args) throws InterruptedException,
        IOException {
    int sum = 0;
    int[] warmup = new int[1];
    warmup[0] = 1;
    for (int i = 0; i < 15000; i++) { // triggers JIT
        sum += copyClone(warmup);
        sum += copyArrayCopy(warmup);
        sum += copyCopyOf(warmup);
    }

    int count = 10_000_000;
    int[] array = new int[count];
    for (int i = 0; i < count; i++) {
        array[i] = i;
    }

    // additional warmup for main
    for (int i = 0; i < 10; i++) {
        sum += copyArrayCopy(array);
    }
    System.gc();
    // copyClone
    long start = System.nanoTime();
    for (int i = 0; i < 10; i++) {
        sum += copyClone(array);
    }
    long end = System.nanoTime();
    System.out.println("clone: " + (end - start) / 1000000);
    System.gc();
    // copyArrayCopy
    start = System.nanoTime();
    for (int i = 0; i < 10; i++) {
        sum += copyArrayCopy(array);
    }
    end = System.nanoTime();
    System.out.println("arrayCopy: " + (end - start) / 1000000);
    System.gc();
    // copyCopyOf
    start = System.nanoTime();
    for (int i = 0; i < 10; i++) {
        sum += copyCopyOf(array);
    }
    end = System.nanoTime();
    System.out.println("Arrays.copyOf: " + (end - start) / 1000000);
    // sum
    System.out.println(sum);
}

private static int copyClone(int[] array) {
    int[] copy = array.clone();
    return copy[copy.length - 1];
}

private static int copyArrayCopy(int[] array) {
    int[] copy = new int[array.length];
    System.arraycopy(array, 0, copy, 0, array.length);
    return copy[copy.length - 1];
}

private static int copyCopyOf(int[] array) {
    int[] copy = Arrays.copyOf(array, array.length);
    return copy[copy.length - 1];
}

【讨论】:

  • ... 结论是 clone() 是最好的选择,它是一个 nilary 方法(没有参数可以搞砸),它也是尽可能安全的(感谢返回类型协变)在clone() 上用于数组类型)。
  • Carrot Search Labs - JUnitBenchmarks 提供了一个 junit 插件,用于执行此类基准测试。它使创建这样的微基准变得非常容易。
  • @C.Trimble 我以前用过caliper,效果很好。
  • @assylias 我刚刚阅读了 caliper 文档,看起来很有趣。
  • Arrays.copyOf 在内部使用 System.arrayCopy,因此无需在它们之间进行比较。
【解决方案3】:

我编写了一个简单的程序来检查差异。

public static void main(String[] args) throws IOException, InterruptedException,
        PrinterException
{
  //Verify array remains immutable.

  String[] str =  {"a","b","c"};
  String[] strings  = str.clone();
  //change returned array
  strings[2]= "d";
  System.out.println(Arrays.toString(str));
  System.out.println(Arrays.toString(strings));

  String[] stringsCopy = Arrays.copyOf(str, str.length);
  stringsCopy[2]= "d";
  System.out.println(Arrays.toString(str));
  System.out.println(Arrays.toString(stringsCopy));

  //peformance
  long before = System.currentTimeMillis();
  for(int i=0;i<Integer.MAX_VALUE;i++)
  {
      str.clone();
  }
  System.out.println("Time Required for Clone: "+ (System.currentTimeMillis()-before));

  //peformance
  long beforeCopy = System.currentTimeMillis();
  for(int i=0;i<Integer.MAX_VALUE;i++)
  {
      Arrays.copyOf(str, str.length);
  }
  System.out.println("Time Required for Copy of: "+ (System.currentTimeMillis()-beforeCopy));

}

然后输出

[a, b, c]
[a, b, d]
[a, b, c]
[a, b, d]
Time Required for Clone: 26288
Time Required for Copy of: 25413

因此,如果您在这两种情况下看到 String[] 是不可变的并且性能几乎相同,则认为 Arrays.copyOf() 在我的机器上稍快。

更新

我将程序改为创建大数组[100 个字符串] 而不是小数组。

  String[] str =  new String[100];

  for(int i= 0; i<str.length;i++)
  {
      str[i]= Integer.toString(i);
  }

并将copy of 方法移到clone 方法之前。结果如下。

 Time Required for Copy of: 415095
 Time Required for Clone: 428501

这又是相同的。 Please do not ask me to run the test again as it takes a while :(

更新 2

对于字符串数组1000000 和迭代次数10000

Time Required for Copy of: 32825
Time Required for Clone: 30138

copy ofclone 花费更多时间

【讨论】:

  • @assylias for Integer.MAX_VALUE 即0x7fffffff
  • 我的意思是你运行了多少次整个基准测试?每次运行的结果可能会有所不同 - 您是否还确保 GC 不会干扰您的结果?
  • @assylias 2 次,是的,它们有所不同,但如果我们取平均值,那么这两种情况几乎相同。
  • 这会多次复制一个非常小的数组。为了强调复制本身而不是围绕它的任何逻辑,最好测试大型数组的复制。
  • @AmitD 如果您切换循环(首先是 Arrays.copyOf,然后是克隆),您会得到相同的结果吗?
【解决方案4】:

还请考虑使用“clone()”的安全性。一类众所周知的攻击使用带有恶意代码覆盖对象的“clone()”方法的类。例如,CVE-2012-0507(Mac OS 上的“闪回”攻击)基本上是由 replacing a ".clone()" call 使用“.copyOf”调用解决的。

关于“clone()”过时的更多讨论可以在 StackOverflow 上找到:object cloning with out implementing cloneable interface

【讨论】:

  • 迟到但出于好奇,可以综合扩展 Array 类型。当然,你不能使用常规的 Java 语法来做到这一点。
  • 看看你也链接的情况,似乎很奇怪它会起作用....也许激进的编译器优化可能导致不检查传递给E[]的值实际上是一个数组因为内联代码只执行所有对象都支持的clone
  • 我明白了,这实际上只能通过称为“type-confusion”的对象指针的低级无效操作来实现。
猜你喜欢
  • 2013-08-23
  • 2014-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
相关资源
最近更新 更多