.toArray(new MyClass )或.toArray(new MyClass )?
假设我有一个ArrayList
ArrayList<MyClass> myList;
我想打电话给arrays,有没有一个性能的原因使用
MyClass[] arr = myList.toArray(new MyClass[myList.size()]);
过度
MyClass[] arr = myList.toArray(new MyClass[0]);
?
我更喜欢第二种风格,因为它不那么冗长,我认为编译器会确保空数组并不真正被创build,但是我一直在想这是不是真的。
当然,在99%的情况下,这种方式并没有什么不同,但是我希望在我的正常代码和优化的内部循环之间保持一致的风格。
从Java 5中的ArrayList开始,如果数组的大小合适(或者更大),则该数组已经被填充。 所以
MyClass[] arr = myList.toArray(new MyClass[myList.size()]);
将创build一个数组对象,填充它并将其返回到“arr”。 另一方面
MyClass[] arr = myList.toArray(new MyClass[0]);
将创build两个数组。 第二个是长度为0的MyClass数组。因此,将立即抛出一个对象的对象创build。 至于源代码build议编译器/ JIT不能优化这个,所以它不会被创build。 此外,使用零长度对象会导致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; }
使用第一种方法,只有一个对象被创build,并避免(隐含但仍然昂贵的)铸件。
反直觉地说,在Hotspot 8上最快的版本是:
MyClass[] arr = myList.toArray(new MyClass[0]);
我已经使用jmh运行了一个微型基准testing的结果和代码如下,显示了一个空数组的版本一贯地胜过带有一个presized数组的版本。 请注意,如果您可以重新使用正确大小的现有数组,结果可能会有所不同。
基准testing结果(以微秒为单位,较小=较好):
Benchmark (n) Mode Samples Score Error Units capSO29378922.preSize 1 avgt 30 0.025 ▒ 0.001 us/op capSO29378922.preSize 100 avgt 30 0.155 ▒ 0.004 us/op capSO29378922.preSize 1000 avgt 30 1.512 ▒ 0.031 us/op capSO29378922.preSize 5000 avgt 30 6.884 ▒ 0.130 us/op capSO29378922.preSize 10000 avgt 30 13.147 ▒ 0.199 us/op capSO29378922.preSize 100000 avgt 30 159.977 ▒ 5.292 us/op capSO29378922.resize 1 avgt 30 0.019 ▒ 0.000 us/op capSO29378922.resize 100 avgt 30 0.133 ▒ 0.003 us/op capSO29378922.resize 1000 avgt 30 1.075 ▒ 0.022 us/op capSO29378922.resize 5000 avgt 30 5.318 ▒ 0.121 us/op capSO29378922.resize 10000 avgt 30 10.652 ▒ 0.227 us/op capSO29378922.resize 100000 avgt 30 139.692 ▒ 8.957 us/op
作为参考,代码:
@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]); } }
现代JVM在这种情况下优化reflectionarrays结构,所以性能差异很小。 在这样的样板代码中两次命名集合不是一个好主意,所以我会避免第一种方法。 第二个优点是它可以与同步和并发的集合一起工作。 如果要进行优化,请重用空数组(空数组是不可变的,并且可以共享),或者使用分析器(!)。
第一种情况更有效率。
这是因为在第二种情况下:
MyClass[] arr = myList.toArray(new MyClass[0]);
运行时实际上创build一个空数组(大小为零),然后在toArray方法内创build另一个数组以适应实际数据。 使用以下代码(取自jdk1.5.0_10)使用reflection来完成此创build:
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; }
通过使用第一种forms,您可以避免创build第二个数组,并避免reflection代码。
toArray检查传递的数组的大小是否合适(即足够大以适应列表中的元素),如果是,则使用该数组。 因此,如果数组的大小提供了小于所需的大小,则会自动创build一个新数组。
在你的情况下,一个大小为零的数组是不可变的,所以可以安全地将其提升到一个静态的最终variables,这可能会使你的代码变得更干净,从而避免在每次调用时创build数组。 无论如何,方法内部将创build一个新的数组,所以这是一个可读性优化。
可以说更快的版本是传递正确大小的数组,但是除非你能certificate这个代码是一个性能瓶颈,否则首选可读性到运行时的性能,直到certificate。
使用第一种情况,因为它更容易,并提供更干净的代码。 这是因为ToArray方法的底层方法是执行O(n)的复制操作。 不变的记忆不是什么大不了的事情。 这些对象的pipe理是非常有效的,它会扩展到所需的大小。
在确定代码中存在瓶颈之前,不要过多地进行优化。 如果你花费太多的时间来优化这个,你只是在浪费时间。 我相信还有很多其他的东西可以优化,所以我会说使用任何一个。 如果你想要可读性和较less冗长的话,就拿第一个。 如果你不介意额外的代码和减less清晰度,使用后者。
在正确大小的数组中使用“toArray”会更好,因为替代方法将首先创build零大小的数组,然后创build正确大小的数组。 但是,正如你所说,差异可能是微不足道的。
另外请注意,javac编译器不会执行任何优化。 现在所有的优化都是由JIT / HotSpot编译器在运行时执行的。 我没有意识到任何JVM中的“toArray”的优化。
那么,你的问题的答案主要是风格问题,但为了一致性的缘故,这应该成为你坚持的任何编码标准的一部分(无论是否logging在案)。
第二个是mor边缘可读的,但有那么一点改进,不值得。 第一种方法更快,在运行时没有缺点,所以这就是我使用的。 但是我把它写成第二种方法,因为input的速度更快。 然后我的IDE将其标记为警告并提供修复。 只需一个按键,它就可以将代码从第二种types转换为第一种types。
整数示例代码:
Integer[] arr = myList.toArray(new integer[0]);