Новая строка String() vs literal string

Этот вопрос задавался много раз в StackOverflow, но ни один из них не был основан на производительности.

В Эффективной 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 наносекунд.

Почему так много улучшений производительности? Существует ли какая-либо оптимизация компилятора внутри?

Ответ 1

В первом случае на каждой итерации создается новый объект, во втором - всегда тот же объект, который извлекается из пула констант String.

В Java, когда вы делаете:

String bla = new String("xpto");

Вы принудительно создаете новый объект String, это занимает некоторое время и память.

С другой стороны, когда вы делаете:

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

Строка будет создана только в первый раз (новый объект), и она будет кэшироваться в пуле констант String, поэтому каждый раз, когда вы ссылаетесь на нее в буквальной форме, вы получаете точное тот же объект, который является удивительно быстрым.

Теперь вы можете спросить... что, если две разные точки в коде извлекают один и тот же литерал и меняют его, нет ли проблем, которые могут возникнуть?!

Нет, потому что Строки на Java, как вы можете очень хорошо знать, неизменны! Таким образом, любая операция, которая будет мутировать String, возвращает новую строку, оставляя любые другие ссылки на один и тот же буквальный счастливый на своем пути.

Это одно из преимуществ неизменяемых структур данных, но это еще одна проблема, и я бы написал пару страниц по этому вопросу.

Edit

Просто пояснение, постоянный пул не является исключительным для типов String, вы можете узнать больше об этом здесь, или если вы Google для постоянного пула 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

Так как c и d являются одним и тем же объектом, сравнение == выполняется.

Ответ 2

Разница в производительности на самом деле намного больше: HotSpot легко компилирует весь цикл

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

поэтому среда выполнения является сплошной 0. Это, однако, происходит только после того, как JIT-компилятор запускается; что для чего требуется прогрев, обязательная процедура, когда microbenchmarking что-либо на 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 очень скоро.

Обратите внимание, что я не перебираю тысячу, а один миллиард раз во внутреннем методе.

Ответ 3

Оба выражения дают объект String, но между ними существует тонкая разница. Когда вы создаете объект String с помощью new(), он всегда создает новый объект в кучевой памяти. С другой стороны, если вы создаете объект, используя синтаксис строкового литерала, например. "Java", он может вернуть существующий объект из пула String (кеш объекта String в пространстве Perm gen, который теперь перемещается в кучу пространства в последнем выпуске Java), если он уже существует. В противном случае он создаст новый строковый объект и добавит пул строк для последующего повторного использования. В остальной части этой статьи, почему это одна из самых важных вещей, которые вы должны запомнить о String в Java.

enter image description here

Ответ 4

как уже было сказано, второй возвращает экземпляр из пула строк (помните, что Строки неизменяемы).

Кроме того, вы должны проверить метод intern(), который позволяет поместить новый String() в пул, если вы не знаете постоянного значения строки во время выполнения: например:

String s = stringVar.intern();

или

new String(stringVar).intern();

Я добавлю дополнительный факт, вы должны знать, что дополнительно к объекту String больше информации существует в пуле (hashcode): это позволяет быстрый поиск hashMap по String в соответствующих данных Strtuctures (вместо воссоздания хэш-кода каждый раз)

Ответ 5

JVM поддерживает пул ссылок на уникальные объекты String, которые являются литералами. В вашем новом примере String вы обертываете литералы экземпляром каждого из них.

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