new String()vs literal string performance

时间:2013-02-07 18:08:05

标签: java string

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 个答案:

答案 0 :(得分:39)

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

在Java中,当你这样做时:

String bla = new String("xpto");

你强制创建一个新的String对象,这需要一些时间和内存。

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

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

String只会在第一次创建(一个新对象),并且会被缓存在String常量池中,因此每次以它的字面形式引用它时,你都会得到它完全相同的对象,速度惊人。

现在您可能会问......如果代码中的两个不同点检索相同的文字并进行更改,会不会出现问题?!

不,因为您可能非常清楚,Java中的字符串是不可变的!因此,任何可以改变String的操作都会返回一个新的String,并保留对同一文字的任何其他引用。

这是不可变数据结构的优势之一,但这完全是另一个问题,我会写几篇关于这个主题的文章。

修改

只是澄清一下,常量池不是String类型所独有的,您可以在这里阅读更多相关信息,或者google for 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是同一个对象,因此==比较成立。

答案 1 :(得分:3)

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

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

不存在所以运行时是一个稳定的0.然而,只有在JIT编译器启动后才会发生这种情况。这就是 warmup 的用途,是对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的效果。

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

答案 2 :(得分:1)

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

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

String s = stringVar.intern();

new String(stringVar).intern();

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

答案 3 :(得分:0)

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

请参阅http://www.precisejava.com/javaperf/j2se/StringAndStringBuffer.htm