考虑以下Java代码片段:
String buffer = "...";
for (int i = 0; i < buffer.length(); i++)
{
System.out.println(buffer.charAt(i));
}
由于String
是不可变的并且buffer
未在循环中重新分配,因此Java编译器是否足够智能以优化for循环条件中的buffer.length()
调用?例如,它是否会发出等效于以下的字节代码,其中buffer.length()
被赋值给变量,并且该变量在循环条件中使用?我已经读过像C#这样的语言进行这种优化。
String buffer = "...";
int length = buffer.length();
for (int i = 0; i < length; i++)
{
System.out.println(buffer.charAt(i));
}
答案 0 :(得分:6)
在Java(和.Net)中,字符串是长度计数(UTF-16代码点的数量),因此查找长度是一个简单的操作。
编译器(javac
)可能会也可能不会执行hoisting,但JVM JIT编译器将almost certainly内联调用.length()
,使buffer.length()
成为{{1}}只不过是一个内存访问。
答案 1 :(得分:1)
Java编译器(javac
)不执行此类优化。 JIT编译器可能会内联length()
方法,这至少可以避免方法调用的开销。
根据您正在运行的JDK,length()
方法本身可能会返回最终length
字段,这是一个便宜的内存访问,或字符串的长度内部char[]
数组。在后一种情况下,数组的长度是常量,并且数组引用可能是final
,因此JIT可能足够复杂,可以根据您的建议在临时中记录一次长度。但是,这种事情是一个实现细节。除非你控制你的代码将运行的每台机器,否则你不应该对它将运行哪个JVM做出太多假设,或者它将执行哪些优化。
至于如何编写代码,在循环条件中直接调用length()
是一种常见的代码模式,并从可读性中受益。我保持简单并让JIT优化器完成其工作,除非您处于已证明性能问题的关键代码路径中,并且您同样证明了这样的微优化是值得的。
答案 2 :(得分:1)
您可以做几件事来检查实施的两种变体。
(难度:容易)在每个版本的代码中,在类似条件下进行测试并测量速度。确保循环足够重要以注意差异,可能没有。
(难度:中)用javap检查字节码并查看编译器如何解释两个版本(这可能因javac实现而异)或者可能不同(当规范中指定了行为并且没有实施者解释的余地。)
(难度:难)用JITWatch检查两个版本的JIT输出,你需要非常了解字节码和汇编程序。