使用预定义大小或无大小的变长参数列表

3
什么是将List转换为varargs的更好方法,以调用下面的“builder(String ... s)”方法?
builder( stringList.toArray( new String[stringList.size()] ) );//with pre-difined array size

或者

builder( stringList.toArray( new String[0] ) );//without predifined size

例如。
void test() {

  List<String> stringList = new ArrayList<>();
  builder( stringList.toArray( new String[stringList.size()] ) );//call builder method with pre-difining array size
  builder( stringList.toArray( new String[0] ) );//call builder method with array size 0

}

void builder( String... s )
{

}

在审查中我遇到了这个问题,有人建议使用builder( stringList.toArray( new String[0] ) )比使用builder( stringList.toArray( new String[stringList.size()] ) )更有效率。这两种方法之间有显著的区别吗?谢谢。


没关系。这是一个微优化问题,对性能没有可感知的影响。使用你认为最易读的方式即可。 - JB Nizet
这要看情况。两种方法的行为不同。第一种方法会在给定数组足够大的情况下使用它。如果不够大,则不使用。因此,如果您使用正确大小的数组,代码将少创建一个对象。这意味着少了一个需要清理的对象。 - Zabuzard
1
Oracle的一位性能工程师对此进行了测试:https://shipilev.net/blog/2016/arrays-wisdom-ancients/#_conclusion - Jorn Vernee
@JornVernee 太棒了!谢谢。 - Dushmantha
@CarlosHeuberger 你的意思是说,当我们调用“new String [0]”时,builder方法再次创建一个数组吗? - Dushmantha
4个回答

5
据我理解
stringList.toArray( new String[stringList.size()] ) )

更高效的原因是:需要为泛型 List<String> 拥有一个实际类型 (String) 的参数,其中泛型类型参数在运行时被擦除。
如果其大小与列表大小匹配,则该参数用于生成结果数组。
如果大小为 0,则传递的数组将被丢弃。
因此,传递正确的数组可以节省一个对象的创建。
当然,会额外调用 list.size(),所以可能会变慢。我怀疑这一点。

更正

请参见古代智慧的数组。 正确的基准测试结果显示,new String[0] 更快。 我浏览了这个非常有趣的分析,似乎:

  • (一个额外的短暂的new String[0]是无关紧要的;)
  • 在toArray方法中进行本地数组复制可以实现不同、更快的数组复制;
  • (然后还有额外的调用size。)

请注意,我没有完全仔细阅读这篇文章;它真的很有趣。

结论(违反直觉):new T[0]更快。

请注意:

  • 代码检查器可能仍然会有不同的看法并发出警告;
  • 这是在预热的情况下:直到热点JIT启动,情况可能会反过来。

1
如果list.size()比其他什么方法(可能相同,所以没有区别)慢的话,那么将使用哪种方法来查找大小呢?实际上,toArray也必须检查大小... - user85421
2
toArray内部将调用list.size()第二次,以检查大小,并可能创建自己的数组。因此会进行一次额外的调用。对于Java SE List实现,其中大小是计数器字段,与new String[0]相比,开销可以忽略不计。但是,某些愚蠢的懒惰的不可变链接列表实现可能没有维护这样的计数器,需要计算每个节点。虽然绝对不太可能发生。我想我不应该提到它。 - Joop Eggen
@JoopEggen,情况正好相反:https://dev59.com/u63la4cB1Zd3GeqPOIVB#51775139 - Eugene
@Eugene,感谢您提供这个非常有趣的链接。在这样基本的情况下得到纠正总是很好的。我已经将其添加到我的答案中。 - Joop Eggen

2
builder(stringList.toArray(new String[0]))稍微不太高效,因为你创建了一个空数组,在方法返回后将被丢弃并永远不会使用。 toArray 将必须创建一个新的数组来存储List的元素。
另一方面,builder(stringList.toArray(new String[stringList.size()]))将一个所需长度的数组传递给 toArray 方法,因此该方法将使用该数组而不是创建一个新数组。

1
好的,除非你进行测量,否则这被称为猜测,有些人证明了它是相反的。https://dev59.com/u63la4cB1Zd3GeqPOIVB#51775139 - Eugene

2

有一个区别,主要由Alexey Shipilev提出。简而言之:

toArray(new T[0]) 看起来更快、更安全、更规范,因此现在应该是默认选择。


1
我认为c.toArray(new String[c.size()]))更有效率,因为我们在这里定义了一个所需大小的数组。
但是!
IntelliJ IDEA有Collection.toArray()检查,默认情况下已启用。这是描述:
有两种方法将集合转换为数组:一种是使用预先大小的数组(如c.toArray(new String[c.size()]))),另一种是使用空数组(如c.toArray(new String[0]))。
在旧版本的Java中,建议使用预先大小的数组,因为创建正确大小的数组所必需的反射调用相当缓慢。然而,自OpenJDK 6的最新更新以来,这个调用已经被内部化,使得空数组版本的性能与预先大小的版本相同,有时甚至更好。同时,对于并发或同步集合,传递预先大小的数组是危险的,因为在size和toArray调用之间可能存在数据竞争,如果集合在操作期间同时缩小,则可能导致额外的null出现在数组末尾。
此检查允许遵循统一的风格:使用空数组(在现代Java中推荐)或使用预先大小的数组(在旧版本的Java或非HotSpot基础的JVM中可能更快)。
看起来,在JDK6之后,我们应该使用c.toArray(new String [0])。 我个人认为,这次使用哪种方法并不重要。 只有当分析器说这是一个瓶颈时,我们才需要担心它。

专业的分析工具和专家已经测试过这个链接:https://shipilev.net/blog/2016/arrays-wisdom-ancients/ - Eugene
@Eugene 谢谢。好链接。 - oleg.cherednik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接