我对这两个习惯用法在实际,现实世界中的实际性能差异最感兴趣。我没有进行微基准测试的经验(它可能不是解决此类问题的正确工具),但我还是尝试了一下。
该基准模拟了一个较为典型的“现实”设置。仅查看返回的数组,然后将其丢弃。没有参考信息,不需要参考相等性。
一个接口,两个实现:
public interface Parser { String[] supportedSchemas(); void parse(String s);}public class NoopParserStaticArray implements Parser { private static final String[] EMPTY_STRING_ARRAY = new String[0]; @Override public String[] supportedSchemas() { return EMPTY_STRING_ARRAY; } @Override public void parse(String s) { s.prePoints().count(); }}public class NoopParserNewArray implements Parser { @Override public String[] supportedSchemas() { return new String[0]; } @Override public void parse(String s) { s.prePoints().count(); }}以及JMH基准测试:
import org.openjdk.jmh.annotations.Benchmark;public class EmptyArrayBenchmark { private static final Parser NOOP_PARSER_STATIC_ARRAY = new NoopParserStaticArray(); private static final Parser NOOP_PARSER_NEW_ARRAY = new NoopParserNewArray(); @Benchmark public void staticEmptyArray() { Parser parser = NOOP_PARSER_STATIC_ARRAY; for (String schema : parser.supportedSchemas()) { parser.parse(schema); } } @Benchmark public void newEmptyArray() { Parser parser = NOOP_PARSER_NEW_ARRAY; for (String schema : parser.supportedSchemas()) { parser.parse(schema); } }}我的机器上的结果Java 1.8.0_51(HotSpot VM):
Benchmark Mode CntScore Error UnitsEmptyArrayBenchmark.staticEmptyArray thrpt 60 3024653836.077 ± 37006870.221 ops/sEmptyArrayBenchmark.newEmptyArray thrpt 60 3018798922.045 ± 33953991.627 ops/sEmptyArrayBenchmark.noop thrpt 60 3046726348.436 ± 5802337.322 ops/s
在这种情况下,两种方法之间没有显着差异。实际上,它们与无操作情况是没有区别的:显然,JIT编译器认识到返回的数组始终为空,并完全优化了循环!
用管道
parser.supportedSchemas()插入黑洞而不是在其上循环,使静态数组实例的方法具有约30%的优势。但是它们的大小绝对相同:
Benchmark Mode CntScore Error UnitsEmptyArrayBenchmark.staticEmptyArray thrpt 60 338971639.355 ± 738069.217 ops/sEmptyArrayBenchmark.newEmptyArray thrpt 60 266936194.767 ± 411298.714 ops/sEmptyArrayBenchmark.noop thrpt 60 3055609298.602 ± 5694730.452 ops/s
最终,答案可能是通常的“取决于情况”。我有一种预感,在许多实际情况下, 将数组创建分解出去的 性能 优势并不明显。
我认为可以这样说
- 如果方法合同使您每次可以自由返回一个新的空数组实例,并且
- 除非您需要避免出现问题性或病理性使用模式和/或追求理论上的最佳性能,
然后
new String[0]直接返回就可以了。
就个人而言,我喜欢的表现力和简洁性
return new String[0];,而不必引入额外的静态场。
出乎我的意料,我写这篇文章一个月后,一位真正的性能工程师调查了这个问题:请参阅AlexeyShipilёv的博客文章“远古智慧的数组”中的这一部分:
如预期的那样, 在很小的集合大小上可以观察到唯一的影响,而这仅是对的边际改进
newFoo[0]。这种改进似乎并不能证明在事物的整体方案中缓存阵列是合理的。作为微小的微优化,在一些严格的代码中可能有意义,但我不在乎。
这样就解决了。我将勾选标记并将其专用于Alexey。



