哪个循环具有更好的性能?为什么?

String s = "";
for(i=0;i<....){
    s = some Assignment;
}

for(i=0;i<..){
    String s = some Assignment;
}

我不需要再在循环之外使用“s”。第一个选项可能更好,因为每次都不会初始化新的字符串。然而,第二种情况会导致变量的作用域被限制为循环本身。

编辑:回应Milhous的回答。将 String 分配给循环中的常量是毫无意义的,不是吗?不,这里的“某些赋值”表示从正在迭代的列表中获取的更改值。

另外,问题不是因为我担心内存管理。只是想知道哪个更好。


答案 1

有限的范围是最好的

使用第二个选项:

for ( ... ) {
  String s = ...;
}

范围不影响性能

如果您从每个代码中反汇编编译的代码(使用JDK的工具),您将看到循环在这两种情况下编译为完全相同的JVM指令。另请注意,Brian R. Bondy的“选项#3”与选项#1相同。使用更严格的范围时,不会在堆栈中添加或删除任何额外的内容,并且在这两种情况下,堆栈上都使用相同的数据。javap

避免过早初始化

这两种情况之间的唯一区别是,在第一个示例中,变量被不必要地初始化。这是一个与变量声明位置不同的问题。这增加了两个浪费的指令(加载字符串常量并将其存储在堆栈帧插槽中)。一个好的静态分析工具会警告你,你永远不会读取分配给 的值,一个好的JIT编译器可能会在运行时省略它。ss

您可以简单地通过使用空声明(即 )来解决此问题,但这被认为是不好的做法,并且下面讨论另一个副作用。String s;

通常,将虚假值(如)分配给变量只是为了掩盖编译器错误,即在未初始化的情况下读取变量。此错误可被视为变量作用域过大的提示,并且在需要接收有效值之前声明该变量作用域。空声明迫使您考虑每个代码路径;不要通过分配虚假值来忽略这个有价值的警告。null

节省堆栈插槽

如前所述,虽然 JVM 指令在这两种情况下都是相同的,但有一个微妙的副作用,使得在 JVM 级别上最好使用尽可能有限的范围。这在方法的“局部变量表”中可见。考虑一下,如果您有多个循环,变量在不必要的大范围内声明,会发生什么情况:

void x(String[] strings, Integer[] integers) {
  String s;
  for (int i = 0; i < strings.length; ++i) {
    s = strings[0];
    ...
  }
  Integer n;
  for (int i = 0; i < integers.length; ++i) {
    n = integers[i];
    ...
  }
}

变量 和 可以在它们各自的循环中声明,但由于它们不是,编译器在堆栈帧中使用两个“槽”。如果它们是在循环内部声明的,则编译器可以重用相同的槽,从而使堆栈帧更小。sn

真正重要的事情

但是,这些问题中的大多数都是无关紧要的。一个好的JIT编译器会发现,不可能读取你浪费分配的初始值,并优化分配。在这里或那里保存插槽不会成就或破坏您的应用程序。

重要的是使你的代码可读且易于维护,在这方面,使用有限的范围显然更好。变量的作用域越小,就越容易理解它的使用方式以及对代码的任何更改会产生什么影响。


答案 2

从理论上讲,在循环中声明字符串是浪费资源。但是,在实践中,您提供的两个代码段将编译为相同的代码(循环外的声明)。

因此,如果您的编译器执行任何数量的优化,则没有区别。