新字符串() 与文本字符串性能

2022-09-02 11:45:26

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

《有效的Java》一书中,它给出了

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

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

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

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纳秒。

为什么会有这么多的性能改进?内部是否有任何编译器优化


答案 1

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

在 Java 中,当您执行以下操作时:

String bla = new String("xpto");

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

另一方面,当您执行以下操作时:

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

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

现在你可能会问...如果代码中的两个不同点检索相同的文本并对其进行更改,那么问题一定会发生吗?!

不,因为字符串,在Java中,你可能很清楚,是不可变的!因此,任何会改变 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);

有了这一切,你可能会找出这些系统的结果:

false
false
true

由于 和 是同一对象,因此比较成立。cd==


答案 2

性能差异实际上要大得多: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 很快就会生效。

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