18

这个问题在 StackOverflow 上被问过很多次,但没有一个是基于性能的。

Effective Java书中,它给出了

如果 String s = new String("stringette");发生在循环或频繁调用的方法中,可能会不必要地创建数百万个 String 实例。

改进的版本如下: String s = "stringette";这个版本使用单个 String 实例,而不是每次执行时都创建一个新实例。

因此,我尝试了两种方法,发现性能有了显着提高

for (int j = 0; j < 1000; j++) {
    String s = new String("hello World");
}

大约需要399 372纳秒。

for (int j = 0; j < 1000; j++) {
    String s = "hello World";
}

大约需要23 000纳秒。

为什么会有这么大的性能提升?里面有没有编译器优化

4

4 回答 4

41

在第一种情况下,每次迭代都会创建一个新对象,在第二种情况下,它始终是同一个对象,从字符串常量池中检索。

在 Java 中,当你这样做时:

String bla = new String("xpto");

您强制创建一个新的 String 对象,这会占用一些时间和内存。

另一方面,当你这样做时:

String muchMuchFaster = "xpto"; //String literal!

String 只会在第一次被创建(一个新对象),它会被缓存在String常量池中,所以每次你以它的字面形式引用它时,你都会得到完全相同的对象,这太神奇了快速地。

现在你可能会问......如果代码中的两个不同点检索相同的文字并更改它,难道不会有问题发生吗?!

不,因为您可能很清楚,Java 中的字符串是不可变的!因此,任何会改变 String 的操作都会返回一个新的 String,而对同一文字的任何其他引用都会在他们的途中感到高兴。

这是不可变数据结构的优点之一,但这完全是另一个问题,我会就此主题写几页。

编辑

只是澄清一下,常量池不是 String 类型独有的,您可以在此处阅读更多信息,或者如果您在谷歌上搜索 Java 常量池。

http://docs.oracle.com/javase/specs/jvms/se7/jvms7.pdf

此外,你可以做一个小测试来把要点带回家:

String a = new String("xpto");
String b = new String("xpto");
String c = "xpto";
String d = "xpto";

System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);

有了这一切,您可能会弄清楚这些 Sysouts 的结果:

false
false
true

因为cd是同一个对象,所以==比较成立。

于 2013-02-07T18:09:28.613 回答
4

性能差异实际上要大得多:HotSpot 可以轻松编译整个循环

for (int j = 0; j < 1000; j++)
{String s="hello World";}

不存在,因此运行时为实数 0。但是,这仅在 JIT 编译器启动后发生;这就是预热的目的,这是在 JVM 上对任何东西进行微基准测试时的强制性过程。

这是我运行的代码:

public static void timeLiteral() {
  for (int j = 0; j < 1_000_000_000; j++)
  {String s="hello World";}
}
public static void main(String... args) {
  for (int i = 0; i < 10; i++) {
    final long start = System.nanoTime();
    timeLiteral();
    System.out.println((System.nanoTime() - start) / 1000);
  }
}

这是一个典型的输出:

1412
38
25
1
1
0
0
1
0
1

您可以观察到 JIT 很快就会生效。

请注意,我不会在内部方法中迭代一千次,而是十亿次。

于 2013-02-07T18:27:04.740 回答
1

正如已经回答的那样,第二个从字符串池中检索实例(记住字符串是不可变的)。

此外,您应该检查 intern() 方法,该方法使您能够将 new String() 放入池中,以防您不知道运行时字符串的常量值:例如:

String s = stringVar.intern();

或者

new String(stringVar).intern();

我将添加额外的事实,您应该知道,除了 String 对象之外,池中还存在更多信息(哈希码):这可以通过字符串在相关数据结构中进行快速 hashMap 搜索(而不是每次都重新创建哈希码)

于 2013-02-07T18:14:05.557 回答
0

JVM 维护一个对作为文字的唯一 String 对象的引用池。在您的新 String 示例中,您将使用每个实例包装文字。

http://www.precisejava.com/javaperf/j2se/StringAndStringBuffer.htm

于 2013-02-07T18:15:16.713 回答