Java .toArray(new MyClass[0]) 还是 .toArray(new MyClass[myList.size()])?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/174093/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 10:53:03  来源:igfitidea点击:

.toArray(new MyClass[0]) or .toArray(new MyClass[myList.size()])?

javaperformancecoding-style

提问by itsadok

Assuming I have an ArrayList

假设我有一个 ArrayList

ArrayList<MyClass> myList;

And I want to call toArray, is there a performance reason to use

我想调用 toArray,是否有使用性能的原因

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

over

超过

MyClass[] arr = myList.toArray(new MyClass[0]);

?

?

I prefer the second style, since it's less verbose, and I assumed that the compiler will make sure the empty array doesn't really get created, but I've been wondering if that's true.

我更喜欢第二种风格,因为它不那么冗长,而且我认为编译器会确保不会真正创建空数组,但我一直想知道这是否属实。

Of course, in 99% of the cases it doesn't make a difference one way or the other, but I'd like to keep a consistent style between my normal code and my optimized inner loops...

当然,在 99% 的情况下,它不会以一种或另一种方式产生影响,但我希望在我的普通代码和优化的内部循环之间保持一致的风格......

采纳答案by assylias

Counterintuitively, the fastest version, on Hotspot 8, is:

与直觉相反,Hotspot 8 上最快的版本是:

MyClass[] arr = myList.toArray(new MyClass[0]);

I have run a micro benchmark using jmh the results and code are below, showing that the version with an empty array consistently outperforms the version with a presized array. Note that if you can reuse an existing array of the correct size, the result may be different.

我已经使用 jmh 运行了一个微型基准测试,结果和代码如下所示,表明带有空数组的版本始终优于带有预大小数组的版本。请注意,如果您可以重用正确大小的现有数组,结果可能会有所不同。

Benchmark results (score in microseconds, smaller = better):

基准测试结果(以微秒为单位的分数,更小 = 更好):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025 ? 0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155 ? 0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512 ? 0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884 ? 0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147 ? 0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977 ? 5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019 ? 0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133 ? 0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075 ? 0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318 ? 0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652 ? 0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692 ? 8.957  us/op


For reference, the code:

供参考,代码:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}


You can find similar results, full analysis, and discussion in the blog post Arrays of Wisdom of the Ancients. To summarize: the JVM and JIT compiler contains several optimizations that enable it to cheaply create and initialize a new correctly sized array, and those optimizations can not be used if you create the array yourself.

您可以在博文Arrays of the Ancients 中找到类似的结果、完整的分析和讨论。总结一下:JVM 和 JIT 编译器包含多项优化,使其能够廉价地创建和初始化新的正确大小的数组,如果您自己创建数组,则无法使用这些优化。

回答by Matthew Murdoch

Using 'toArray' with the array of the correct size will perform better as the alternative will create first the zero sized array then the array of the correct size. However, as you say the difference is likely to be negligible.

将“toArray”与正确大小的数组一起使用会更好,因为替代方法将首先创建零大小的数组,然后创建正确大小的数组。但是,正如您所说,差异可能可以忽略不计。

Also, note that the javac compiler does not perform any optimization. These days all optimizations are performed by the JIT/HotSpot compilers at runtime. I am not aware of any optimizations around 'toArray' in any JVMs.

另请注意,javac 编译器不执行任何优化。如今,所有优化都由 JIT/HotSpot 编译器在运行时执行。我不知道任何 JVM 中围绕“toArray”的任何优化。

The answer to your question, then, is largely a matter of style but for consistency's sake should form part of any coding standards you adhere to (whether documented or otherwise).

那么,您的问题的答案主要是风格问题,但为了一致性起见,应该构成您遵守的任何编码标准的一部分(无论是否记录在案)。

回答by Panagiotis Korros

The first case is more efficient.

第一种情况更有效。

That is because in the second case:

那是因为在第二种情况下:

MyClass[] arr = myList.toArray(new MyClass[0]);

the runtime actually creates an empty array (with zero size) and then inside the toArray method creates another array to fit the actual data. This creation is done using reflection using the following code (taken from jdk1.5.0_10):

运行时实际上创建了一个空数组(大小为零),然后在 toArray 方法内部创建另一个数组以适应实际数据。使用以下代码(取自 jdk1.5.0_10)使用反射完成此创建:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

By using the first form, you avoid the creation of a second array and also avoid the reflection code.

通过使用第一种形式,您可以避免创建第二个数组并避免反射代码。

回答by Dave Cheney

toArray checks that the array passed is of the right size (that is, large enough to fit the elements from your list) and if so, uses that. Consequently if the size of the array provided it smaller than required, a new array will be reflexively created.

toArray 检查传递的数组大小是否正确(即足够大以适合列表中的元素),如果是,则使用它。因此,如果提供的数组大小小于所需的大小,则会反射性地创建一个新数组。

In your case, an array of size zero, is immutable, so could safely be elevated to a static final variable, which might make your code a little cleaner, which avoids creating the array on each invocation. A new array will be created inside the method anyway, so it's a readability optimisation.

在您的情况下,大小为零的数组是不可变的,因此可以安全地提升为静态最终变量,这可能会使您的代码更简洁,从而避免在每次调用时创建数组。无论如何,都会在方法内部创建一个新数组,因此这是一种可读性优化。

Arguably the faster version is to pass the array of a correct size, but unless you can provethis code is a performance bottleneck, prefer readability to runtime performance until proven otherwise.

可以说更快的版本是传递正确大小的数组,但除非您能证明此代码是性能瓶颈,否则在证明其他情况之前,更喜欢可读性而不是运行时性能。

回答by Georgi

As of ArrayList in Java 5, the array will be filled already if it has the right size (or is bigger). Consequently

Java 5 中ArrayList开始,如果数组具有正确的大小(或更大),它将已经被填充。最后

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

will create one array object, fill it and return it to "arr". On the other hand

将创建一个数组对象,填充它并将其返回到“arr”。另一方面

MyClass[] arr = myList.toArray(new MyClass[0]);

will create two arrays. The second one is an array of MyClass with length 0. So there is an object creation for an object that will be thrown away immediately. As far as the source code suggests the compiler / JIT cannot optimize this one so that it is not created. Additionally, using the zero-length object results in casting(s) within the toArray() - method.

将创建两个数组。第二个是长度为 0 的 MyClass 数组。因此,为将立即丢弃的对象创建了一个对象。就源代码而言,编译器/JIT 无法优化它,因此不会创建它。此外,使用零长度对象会导致在 toArray() - 方法中进行强制转换。

See the source of ArrayList.toArray():

看ArrayList.toArray()的源码:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Use the first method so that only one object is created and avoid (implicit but nevertheless expensive) castings.

使用第一种方法,以便只创建一个对象并避免(隐式但昂贵的)强制转换。

回答by Tom Hawtin - tackline

Modern JVMs optimise reflective array construction in this case, so the performance difference is tiny. Naming the collection twice in such boilerplate code is not a great idea, so I'd avoid the first method. Another advantage of the second is that it works with synchronised and concurrent collections. If you want to make optimisation, reuse the empty array (empty arrays are immutable and can be shared), or use a profiler(!).

在这种情况下,现代 JVM 优化了反射阵列构造,因此性能差异很小。在这样的样板代码中两次命名集合并不是一个好主意,所以我会避免使用第一种方法。第二个的另一个优点是它适用于同步和并发集合。如果要进行优化,请重用空数组(空数组不可变且可以共享),或使用分析器(!)。

回答by Rasol

sample code for integer :

整数示例代码:

Integer[] arr = myList.toArray(new integer[0]);

回答by MiguelMunoz

The second one is marginally mor readable, but there so little improvement that it's not worth it. The first method is faster, with no disadvantages at runtime, so that's what I use. But I write it the second way, because it's faster to type. Then my IDE flags it as a warning and offers to fix it. With a single keystroke, it converts the code from the second type to the first one.

第二个稍微更具可读性,但几乎没有改进,因此不值得。第一种方法更快,在运行时没有缺点,所以这就是我使用的。但我用第二种方式写,因为打字速度更快。然后我的 IDE 将其标记为警告并提供修复它。只需一次击键,它就会将代码从第二种类型转换为第一种类型。

回答by Антон Антонов

From JetBrains Intellij Idea inspection:

来自 JetBrains Intellij Idea 检查:

There are two styles to convert a collection to an array: either using a pre-sized array (like c.toArray(new String[c.size()])) or using an empty array (like c.toArray(new String[0]).

In older Java versions using pre-sized array was recommended, as the reflection call which is necessary to create an array of proper size was quite slow. However since late updates of OpenJDK 6 this call was intrinsified, making the performance of the empty array version the same and sometimes even better, compared to the pre-sized version. Also passing pre-sized array is dangerous for a concurrent or synchronized collection as a data race is possible between the sizeand toArraycall which may result in extra nulls at the end of the array, if the collection was concurrently shrunk during the operation.

This inspection allows to follow the uniform style: either using an empty array (which is recommended in modern Java) or using a pre-sized array (which might be faster in older Java versions or non-HotSpot based JVMs).

有两种样式可以将集合转换为数组:使用预先确定大小的数组(如c.toArray(new String[c.size()]))或使用空数组(如c.toArray(new String[ 0])

在较旧的 Java 版本中,建议使用预先确定大小的数组,因为创建适当大小的数组所需的反射调用非常慢。然而,由于 OpenJDK 6 的后期更新,这个调用被内化了,使得空数组版本的性能与预先调整大小的版本相同,有时甚至更好。此外,传递预先确定大小的数组对于并发或同步集合是危险的,因为sizetoArray调用之间可能会发生数据竞争 ,如果集合在操作期间并发收缩,这可能会导致数组末尾出现额外的空值。

此检查允许遵循统一样式:使用空数组(现代 Java 中推荐使用)或使用预先确定大小的数组(在较旧的 Java 版本或非基于 HotSpot 的 JVM 中可能更快)。