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

或者

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

我再也不需要在循环外使用's'了。第一个选项可能更好,因为每次都不会初始化新的字符串。然而,第二个将导致变量的范围仅限于循环本身。

编辑:回应 Milhous 的回答。将字符串分配给循环内的常量是没有意义的,不是吗?不,这里的“一些分配”是指从被迭代的列表中获得的变化值。

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

4

8 回答 8

109

有限的范围是最好的

使用您的第二个选项:

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

范围不影响性能

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

避免过早初始化

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

您可以简单地通过使用空声明(即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 编译器会发现无法读取您浪费分配的初始值,并优化分配。在这里或那里保存一个插槽不会成就或破坏您的应用程序。

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

于 2008-09-21T06:24:46.287 回答
22

理论上,在循环内声明字符串是一种资源浪费。然而,在实践中,您提供的两个片段都将编译为相同的代码(循环外的声明)。

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

于 2008-09-21T02:48:05.650 回答
17

一般来说,我会选择第二个,因为 's' 变量的范围仅限于循环。好处:

  • 这对程序员来说更好,因为您不必担心在函数稍后的某个地方再次使用 's'
  • 这对编译器来说更好,因为变量的范围更小,因此它可以潜在地进行更多的分析和优化
  • 这对未来的读者来说更好,因为他们不会想知道为什么's'变量在循环之外声明,如果它以后从未使用过
于 2008-09-21T02:51:43.613 回答
6

如果你想加快 for 循环,我更喜欢在计数器旁边声明一个 max 变量,这样就不需要重复查找条件:

代替

for (int i = 0; i < array.length; i++) {
  Object next = array[i];
}

我更喜欢

for (int i = 0, max = array.lenth; i < max; i++) {
  Object next = array[i];
}

任何其他应该考虑的事情都已经提到过,所以只是我的两分钱(见 ericksons 帖子)

格瑞兹,GHad

于 2008-09-21T13:55:10.087 回答
4

为了在@ Esteban Araya 的答案中添加一点,它们都需要每次通过循环创建一个新字符串(作为some Assignment表达式的返回值)。无论哪种方式,这些字符串都需要进行垃圾收集。

于 2008-09-21T02:54:17.647 回答
3

我知道这是一个老问题,但我想我会添加一些稍微相关的内容。

在浏览 Java 源代码时,我注意到某些方法,如 String.contentEquals(在下面重复)会生成冗余的局部变量,这些变量只是类变量的副本。我相信某处有一条评论,暗示访问局部变量比访问类变量更快。

在这种情况下,“v1”和“v2”似乎是不必要的,可以删除以简化代码,但添加是为了提高性能。

public boolean contentEquals(StringBuffer sb) {
    synchronized(sb) {
        if (count != sb.length())
            return false;
        char v1[] = value;
        char v2[] = sb.getValue();
        int i = offset;
        int j = 0;
        int n = count;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
                return false;
        }
    }
    return true;
}
于 2009-04-13T20:13:30.610 回答
1

在我看来,我们需要更多地说明问题。

s = some Assignment;

没有具体说明这是什么类型的任务。如果任务是

s = "" + i + "";

然后需要分配一个新的刺。

但如果是

s = some Constant;

s 将仅指向常量内存位置,因此第一个版本的内存效率更高。

似乎我有点愚蠢地担心为解释的语言恕我直言的 for 循环的大量优化。

于 2008-09-21T05:32:17.363 回答
1

当我使用多个线程(50+)时,我发现这是处理无法正确关闭进程的幽灵线程问题的一种非常有效的方法....如果我错了,请告诉我原因我错了:

Process one;
BufferedInputStream two;
try{
one = Runtime.getRuntime().exec(command);
two = new BufferedInputStream(one.getInputStream());
}
}catch(e){
e.printstacktrace
}
finally{
//null to ensure they are erased
one = null;
two = null;
//nudge the gc
System.gc();
}
于 2013-07-29T21:10:43.897 回答