93

我想创建一个新的对象数组,将两个较小的数组放在一起。

它们不能为空,但大小可能为 0。

我无法在这两种方式之间进行选择:它们是等效的还是更有效的一种(例如 system.arraycopy() 复制整个块)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

或者

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

唯一的区别是代码的外观吗?

编辑:感谢链接的问题,但他们似乎有一个未解决的讨论:

it is not for native typesif : byte[], Object[], char[]真的更快吗?在所有其他情况下,将执行类型检查,这将是我的情况,因此将是等效的......不是吗?

在另一个链接的问题上,他们说the size matters a lot,对于大于 24 的 system.arraycopy() 获胜,对于小于 10,手动 for 循环更好......

现在我真的很困惑。

4

7 回答 7

90
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

我知道 JUnit 测试对于基准测试来说并不是最好的,但是
testHardCopyBytes 需要 0.157 秒才能完成
,而
testArrayCopyBytes 需要 0.086 秒才能完成。

我认为这取决于虚拟机,但它看起来好像复制内存块而不是复制单个数组元素。这绝对会提高性能。

编辑:
看起来 System.arraycopy 的表现无处不在。当使用字符串而不是字节并且数组很小(大小为 10)时,我得到以下结果:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

这是数组大小为 0x1000000 时的样子。看起来 System.arraycopy 肯定会在更大的数组中获胜。

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

多么奇特!

感谢 Daren 指出引用的复制方式不同。它使这成为一个更有趣的问题!

于 2013-09-05T14:28:28.900 回答
36

Arrays.copyOf(T[], int)更容易阅读。它使用的内部System.arraycopy()是本机调用。

你不能更快地得到它!

于 2013-09-05T14:19:05.800 回答
18

这取决于虚拟机,但 System.arraycopy 应该为您提供最接近本机性能的方法。

作为嵌入式系统(性能是重中之重)的 Java 开发人员,我已经工作了 2 年,在任何可以使用 System.arraycopy 的地方,我主要使用它/看到它在现有代码中使用。当性能成为问题时,它总是优于循环。如果性能不是一个大问题,我会选择循环。更容易阅读。

于 2013-09-05T14:18:45.433 回答
18

我没有依赖推测和可能过时的信息,而是使用运行了一些基准测试。事实上,Caliper 附带了一些示例,包括一个CopyArrayBenchmark可以准确测量这个问题的示例!你所要做的就是跑

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

我的结果基于 Oracle 的 Java HotSpot(TM) 64 位服务器 VM,1.8.0_31-b13,在 2010 年中期的 MacBook Pro(macOS 10.11.6 和 Intel Arrandale i7,8 GiB RAM)上运行。我认为发布原始计时数据没有用。相反,我将用支持的可视化来总结结论。

总之:

  • 编写手动for循环将每个元素复制到新实例化的数组中从来都不是有利的,无论是对于短数组还是长数组。
  • Arrays.copyOf(array, array.length)并且array.clone()都始终如一。这两种技术在性能上几乎相同;你选择哪一个是一个品味问题。
  • System.arraycopy(src, 0, dest, 0, src.length)几乎与 和 一样快,但并不完全一致。(请参阅 50000秒的案例。)正因为如此,以及调用的冗长,如果您需要精细控制哪些元素被复制到哪里,我会建议您。Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

以下是时序图:

复制长度为 5 的数组的时间 复制长度为 500 的数组的时间 复制长度为 50000 的数组的时间

于 2018-03-14T04:36:24.403 回答
8

不可能Arrays.copyOf比它更快,System.arraycopy因为这是以下实现copyOf

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
于 2018-05-10T11:37:04.527 回答
6

像这样执行本机方法Arrays.copyOf(T[], int)确实有一些开销,但这并不意味着它不像您使用 JNI 执行它那样快。

最简单的方法是编写基准和测试。

您可以检查它Arrays.copyOf(T[], int)是否比正常for循环更快。

这里的基准代码:-

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()使用 JNI(Java 本地接口)来复制数组(或其中的一部分),因此速度非常快,您可以 在此处确认

于 2013-09-05T14:27:17.237 回答
4

System.arraycopy()是一个本机调用,它直接在内存中进行复制操作。单个内存副本总是比你的 for 循环快

于 2013-09-05T14:21:55.217 回答