Java .toArray(新MyClass[0])或.toArray(新MyClass[myList.size()])?
假设我有一个ArrayListJava .toArray(新MyClass[0])或.toArray(新MyClass[myList.size()])?,java,performance,coding-style,Java,Performance,Coding Style,假设我有一个ArrayList ArrayList<MyClass> myList; 结束 ? 我更喜欢第二种样式,因为它不太冗长,我假设编译器将确保不会真正创建空数组,但我一直在想这是不是真的 当然,在99%的情况下,这两者都没有区别,但是我想在我的普通代码和优化的内部循环之间保持一致的风格…使用“toArray”和正确大小的数组将执行得更好,因为替代方法将首先创建零大小的数组,然后创建正确大小的数组。然而,正如你所说,这种差异可能是可以忽略不计的 另外,请注意javac编译器不
ArrayList<MyClass> myList;
结束
?
我更喜欢第二种样式,因为它不太冗长,我假设编译器将确保不会真正创建空数组,但我一直在想这是不是真的
当然,在99%的情况下,这两者都没有区别,但是我想在我的普通代码和优化的内部循环之间保持一致的风格…使用“toArray”和正确大小的数组将执行得更好,因为替代方法将首先创建零大小的数组,然后创建正确大小的数组。然而,正如你所说,这种差异可能是可以忽略不计的 另外,请注意javac编译器不执行任何优化。现在所有的优化都是由JIT/HotSpot编译器在运行时执行的。我不知道在任何JVM中有任何关于“toArray”的优化
那么,您的问题的答案在很大程度上取决于风格,但出于一致性的考虑,应该成为您所遵循的任何编码标准的一部分(无论是否有文档记录)。第一种情况更有效 这是因为在第二种情况下:
MyClass[] arr = myList.toArray(new MyClass[0]);
运行时实际上创建了一个空数组(大小为零),然后在toArray方法内部创建另一个数组以适合实际数据。此创建使用反射完成,使用以下代码(取自jdk1.5.0_10):
public T[]toArray(T[]a){
如果(a.长度<尺寸)
a=(T[])java.lang.reflect.Array。
newInstance(a.getClass().getComponentType(),大小);
System.arraycopy(elementData,0,a,0,size);
如果(a.长度>尺寸)
a[size]=null;
返回a;
}
通过使用第一个表单,您可以避免创建第二个数组,也可以避免反射代码。toArray检查传递的数组是否大小正确(即足够大,可以容纳列表中的元素),如果是,则使用该数组。因此,如果提供的数组的大小小于所需的大小,则会反射性地创建一个新数组 在您的例子中,大小为零的数组是不可变的,因此可以安全地提升为静态最终变量,这可能会使您的代码更干净,从而避免在每次调用时创建数组。无论如何,一个新的数组将在方法内部创建,因此这是一个可读性优化 可以说,更快的版本是传递正确大小的数组,但除非您能证明此代码是性能瓶颈,否则在证明其他版本之前,您更喜欢可读性而不是运行时性能。截至,如果数组大小正确(或更大),则数组将已填充。因此
MyClass[] arr = myList.toArray(new MyClass[myList.size()]);
将创建一个数组对象,填充它并将其返回到“arr”。另一方面
MyClass[] arr = myList.toArray(new MyClass[0]);
将创建两个数组。第二个是长度为0的MyClass数组。因此,有一个对象创建的对象将被立即扔掉。根据源代码的建议,编译器/JIT无法优化此代码,因此无法创建它。此外,使用零长度对象会导致在toArray()方法中进行强制转换
请参阅ArrayList.toArray()的源代码:
public T[]toArray(T[]a){
如果(a.长度<尺寸)
//创建a的运行时类型的新数组,但我的内容:
return(T[])array.copyOf(elementData,size,a.getClass());
System.arraycopy(elementData,0,a,0,size);
如果(a.长度>尺寸)
a[size]=null;
返回a;
}
使用第一种方法,只创建一个对象,避免(隐式但昂贵)铸件。现代JVM在这种情况下优化反射阵列结构,因此性能差异很小。在这样的样板代码中为集合命名两次不是一个好主意,所以我会避免使用第一种方法。第二种方法的另一个优点是它可以处理同步和并发的集合。如果要进行优化,请重用空数组(空数组是不可变的,可以共享),或者使用探查器(!)。整数的示例代码:
Integer[] arr = myList.toArray(new integer[0]);
第二个版本的可读性稍差,但改进太少,不值得。第一种方法速度更快,在运行时没有缺点,所以这就是我使用的方法。但是我用第二种方式写,因为它打字更快。然后我的IDE将其标记为警告并提供修复。只需一次按键,即可将代码从第二种类型转换为第一种类型。与直觉相反,Hotspot 8上最快的版本是:
MyClass[] arr = myList.toArray(new MyClass[0]);
我已经使用jmh运行了一个微基准测试。结果和代码如下,表明使用空数组的版本始终优于使用预大小数组的版本。请注意,如果可以重用大小正确的现有数组,结果可能会有所不同
基准测试结果(分数以微秒为单位,越小越好):
以下守则供参考:
@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]);
}
}
@状态(Scope.Thread)
@基准模式(模式平均时间)
公共类SO29378922{
@参数({“1”、“100”、“1000”、“5000”、“10000”、“100000”)int n;
私有最终列表=新的ArrayList();
@设置公共void populateList(){
对于(inti=0;i
你可以在博客文章中找到类似的结果、完整的分析和讨论。总而言之:JVM和JIT编译器包含几种优化,使其能够以较低的成本创建和初始化新的大小正确的数组,如果您自己创建数组,则无法使用这些优化。来自JetBrains Intellij Idea inspection: 将集合转换为数组有两种样式:使用 预先调整大小的数组(如c.toArray(新字符串[c.size()])或 使用空数组(如c.toArray(新字符串[0])。In 古老的
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;
}
Integer[] arr = myList.toArray(new integer[0]);
MyClass[] arr = myList.toArray(new MyClass[0]);
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
@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]);
}
}