Что такое пул строк Java и как он отличается от нового String («s»)?

Что означает String Pool ? И в чем разница между следующими объявлениями:

String s = "hello"; String s = new String("hello"); 

Есть ли разница между сохранением этих двух строк JVM?

Пул строк – это конкретная реализация JVM концепции интернирования строк :

В информатике интернирование строк – это метод хранения только одной копии каждого отдельного строкового значения, которое должно быть неизменным. Внутренние строки делают некоторые задачи обработки строк более экономными по времени или пространству за счет того, что требуется больше времени при создании или интернировании строки. Разные значения хранятся в пуле пула.

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

В качестве интересной заметки, интернирование строк – пример шаблона дизайна мухи :

Flyweight – это шаблон программного обеспечения. Мухи – это объект, который минимизирует использование памяти путем совместного использования как можно большего количества данных с другими подобными объектами; это способ использовать объекты в больших количествах, когда простое повторное представление будет использовать недопустимый объем памяти.

Пул строк позволяет использовать постоянные константы, что возможно, потому что строки в Java неизменяемы. Если вы повторяете одну и ту же строчную константу по всему месту вашего Java-кода, на самом деле вы можете иметь только одну копию этой строки в вашей системе, что является одним из преимуществ этого механизма.

Когда вы используете String s = "string constant"; вы получаете копию, которая находится в пуле строк. Однако, когда вы делаете String s = new String("string constant"); вы принудительно распределяете копию.

JLS

Как упоминалось Эндрю , эта концепция называется «интернированием» JLS.

Соответствующий переход от JLS 7 3.10.5 :

Более того, строковый литерал всегда ссылается на тот же экземпляр classа String. Это связано с тем, что строковые литералы, или, в более общем смысле, строки, которые являются значениями константных выражений (§15.28), «интернированы», чтобы обмениваться уникальными экземплярами, используя метод String.intern.

Пример 3.10.5-1. Строковые литералы

Программа, состоящая из блока компиляции (§7.3):

 package testPackage; class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.print((hello == "Hello") + " "); System.out.print((Other.hello == hello) + " "); System.out.print((other.Other.hello == hello) + " "); System.out.print((hello == ("Hel"+"lo")) + " "); System.out.print((hello == ("Hel"+lo)) + " "); System.out.println(hello == ("Hel"+lo).intern()); } } class Other { static String hello = "Hello"; } 

и блок компиляции:

 package other; public class Other { public static String hello = "Hello"; } 

производит выход:

 true true true true false true 

JVMs

JVMS 7 5.1 говорит :

Строковый литерал является ссылкой на экземпляр classа String и выводится из структуры CONSTANT_String_info (§4.4.3) в двоичном представлении classа или интерфейса. Структура CONSTANT_String_info дает последовательность кодовых точек Unicode, составляющих строковый литерал.

Язык программирования Java требует, чтобы идентичные строковые литералы (то есть литералы, которые содержат одну и ту же последовательность кодовых точек) должны относиться к одному экземпляру classа String (JLS §3.10.5). Кроме того, если метод String.intern вызывается в любой строке, результатом является ссылка на тот же экземпляр classа, который будет возвращен, если эта строка появилась как литерал. Таким образом, следующее выражение должно иметь значение true:

 ("a" + "b" + "c").intern() == "abc" 

Чтобы получить строковый литерал, виртуальная машина Java проверяет последовательность кодовых точек, заданную структурой CONSTANT_String_info.

  • Если метод String.intern ранее был вызван в экземпляр classа String, содержащий последовательность кодовых точек Unicode, идентичную последовательности, заданной структурой CONSTANT_String_info, тогда результат строкового литерала является ссылкой на тот же экземпляр classа String.

  • В противном случае создается новый экземпляр classа String, содержащий последовательность кодовых точек Unicode, заданную структурой CONSTANT_String_info; ссылка на этот экземпляр classа является результатом строкового литерала. Наконец, вызывается метод intern нового экземпляра String.

Bytecode

Поучительно также взглянуть на реализацию байт-кода на OpenJDK 7.

Если мы декомпилируем:

 public class StringPool { public static void main(String[] args) { String a = "abc"; String b = "abc"; String c = new String("abc"); System.out.println(a); System.out.println(b); System.out.println(a == c); } } 

у нас есть постоянный пул:

 #2 = String #32 // abc [...] #32 = Utf8 abc 

и main :

  0: ldc #2 // String abc 2: astore_1 3: ldc #2 // String abc 5: astore_2 6: new #3 // class java/lang/String 9: dup 10: ldc #2 // String abc 12: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V 15: astore_3 16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 19: aload_1 20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_2 27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 33: aload_1 34: aload_3 35: if_acmpne 42 38: iconst_1 39: goto 43 42: iconst_0 43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 

Обратите внимание, как:

  • 0 и 3 : ldc #2 и та же ldc #2 (литералы)
  • 12 : создается новый экземпляр строки (с аргументом #2 качестве аргумента)
  • 35 : a и c сравниваются как обычные объекты с if_acmpne

Представление постоянных строк довольно магия на байт-коде:

  • у него есть специальная структура CONSTANT_String_info , в отличие от обычных объектов (например, new String )
  • структура указывает на структуру CONSTANT_Utf8_info , содержащую данные. Это единственные необходимые данные для представления строки.

и приведенная выше цитата JVMS, похоже, говорит, что всякий раз, когда указатель Utf8 одинаковый, то идентичные экземпляры загружаются ldc .

Я сделал аналогичные тесты для полей и:

  • static final String s = "abc" указывает на таблицу констант через атрибут ConstantValue
  • не конечные поля не имеют этого атрибута, но все еще могут быть инициализированы с помощью ldc

Вывод : имеется прямая поддержка байт-кода для пула строк, и представление памяти является эффективным.

Бонус: сравните это с пулом Integer , который не имеет прямой поддержки байт-кода (т. CONSTANT_String_info Аналога CONSTANT_String_info ).

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

Когда вы используете литерал, скажите String str = "abc"; , используется объект в пуле. Если вы используете String str = new String("abc"); , создается новый объект, но существующий строковый литерал может повторно использоваться либо на уровне JVM, либо на уровне байт-кода (во время компиляции).

Вы можете проверить это для себя, создав много строк в цикле for и используя оператор == для проверки равенства объектов. В следующем примере string.value является private для String и содержит строковый литерал. Поскольку он является закрытым, к нему нужно получить доступ через reflection.

 public class InternTest { public static void main(String[] args) { String rehi = "rehi"; String rehi2 = "rehi"; String rehi2a = "not rehi"; String rehi3 = new String("rehi"); String rehi3a = new String("not rehi"); String rehi4 = new String(rehi); String rehi5 = new String(rehi2); String rehi6 = new String(rehi2a); String[] arr = new String[] { rehi, rehi2, rehi2a, rehi3, rehi3a, rehi4, rehi5, rehi6 }; String[] arr2 = new String[] { "rehi", "rehi (2)", "not rehi", "new String(\"rehi\")", "new String(\"not rehi\")", "new String(rehi)", "new String(rehi (2))", "new String(not rehi)" }; Field f; try { f = String.class.getDeclaredField("value"); f.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr.length; j++) { System.out.println("i: " +arr2[i]+", j: " +arr2[j]); System.out.println("i==j: " + (arr[i] == arr[j])); System.out.println("i equals j: " + (arr[i].equals(arr[j]))); try { System.out.println("i.value==j.value: " + (f.get(arr[i]) == f.get(arr[j]))); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } System.out.println("========"); } } } } 

Вывод:

 i: rehi, j: rehi i==j: true i equals j: true i.value==j.value: true ======== i: rehi, j: rehi (2) i==j: true i equals j: true i.value==j.value: true ======== i: rehi, j: not rehi i==j: false i equals j: false i.value==j.value: false ======== i: rehi, j: new String("rehi") i==j: false i equals j: true i.value==j.value: true ======== i: rehi, j: new String("not rehi") i==j: false i equals j: false i.value==j.value: false ======== i: rehi, j: new String(rehi) i==j: false i equals j: true i.value==j.value: true ======== i: rehi, j: new String(rehi (2)) i==j: false i equals j: true i.value==j.value: true ======== i: rehi, j: new String(not rehi) i==j: false i equals j: false i.value==j.value: false ======== i: rehi (2), j: rehi i==j: true i equals j: true i.value==j.value: true ======== i: rehi (2), j: rehi (2) i==j: true i equals j: true i.value==j.value: true ======== i: rehi (2), j: not rehi i==j: false i equals j: false i.value==j.value: false ======== i: rehi (2), j: new String("rehi") i==j: false i equals j: true i.value==j.value: true ======== i: rehi (2), j: new String("not rehi") i==j: false i equals j: false i.value==j.value: false ======== i: rehi (2), j: new String(rehi) i==j: false i equals j: true i.value==j.value: true ======== i: rehi (2), j: new String(rehi (2)) i==j: false i equals j: true i.value==j.value: true ======== i: rehi (2), j: new String(not rehi) i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: rehi i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: rehi (2) i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: not rehi i==j: true i equals j: true i.value==j.value: true ======== i: not rehi, j: new String("rehi") i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: new String("not rehi") i==j: false i equals j: true i.value==j.value: true ======== i: not rehi, j: new String(rehi) i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: new String(rehi (2)) i==j: false i equals j: false i.value==j.value: false ======== i: not rehi, j: new String(not rehi) i==j: false i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: rehi i==j: false i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: rehi (2) i==j: false i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: not rehi i==j: false i equals j: false i.value==j.value: false ======== i: new String("rehi"), j: new String("rehi") i==j: true i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: new String("not rehi") i==j: false i equals j: false i.value==j.value: false ======== i: new String("rehi"), j: new String(rehi) i==j: false i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: new String(rehi (2)) i==j: false i equals j: true i.value==j.value: true ======== i: new String("rehi"), j: new String(not rehi) i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: rehi i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: rehi (2) i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: not rehi i==j: false i equals j: true i.value==j.value: true ======== i: new String("not rehi"), j: new String("rehi") i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: new String("not rehi") i==j: true i equals j: true i.value==j.value: true ======== i: new String("not rehi"), j: new String(rehi) i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: new String(rehi (2)) i==j: false i equals j: false i.value==j.value: false ======== i: new String("not rehi"), j: new String(not rehi) i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi), j: rehi i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi), j: rehi (2) i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi), j: not rehi i==j: false i equals j: false i.value==j.value: false ======== i: new String(rehi), j: new String("rehi") i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi), j: new String("not rehi") i==j: false i equals j: false i.value==j.value: false ======== i: new String(rehi), j: new String(rehi) i==j: true i equals j: true i.value==j.value: true ======== i: new String(rehi), j: new String(rehi (2)) i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi), j: new String(not rehi) i==j: false i equals j: false i.value==j.value: false ======== i: new String(rehi (2)), j: rehi i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi (2)), j: rehi (2) i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi (2)), j: not rehi i==j: false i equals j: false i.value==j.value: false ======== i: new String(rehi (2)), j: new String("rehi") i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi (2)), j: new String("not rehi") i==j: false i equals j: false i.value==j.value: false ======== i: new String(rehi (2)), j: new String(rehi) i==j: false i equals j: true i.value==j.value: true ======== i: new String(rehi (2)), j: new String(rehi (2)) i==j: true i equals j: true i.value==j.value: true ======== i: new String(rehi (2)), j: new String(not rehi) i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: rehi i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: rehi (2) i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: not rehi i==j: false i equals j: true i.value==j.value: true ======== i: new String(not rehi), j: new String("rehi") i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: new String("not rehi") i==j: false i equals j: true i.value==j.value: true ======== i: new String(not rehi), j: new String(rehi) i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: new String(rehi (2)) i==j: false i equals j: false i.value==j.value: false ======== i: new String(not rehi), j: new String(not rehi) i==j: true i equals j: true i.value==j.value: true ======== 

Его озадачивало то, что никто не отвечал прямо на вопрос, но большинство ответов имеют много оборотов.

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

Оба объекта живут в куче. Ссылки на оба будут в стеке streamа.

http://www.journaldev.com/797/what-is-java-string-pool дает четкое представление о том, как это достигается

JVM выполняет некоторые обманки при создании строковых литералов, чтобы повысить производительность и уменьшить накладные расходы памяти. Чтобы сократить количество объектов String, созданных в JVM, class String содержит пул строк. Каждый раз, когда ваш код создает строковый литерал, JVM сначала проверяет пул строк. Если строка уже существует в пуле, возвращается ссылка на объединенный экземпляр. Если строка не существует в пуле, создается новый объект String, а затем помещается в пул.

  public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; System.out.print(str1 == str2); } } 

Выход: true

К сожалению, когда вы используете

 String a=new String("Hello"); 

Объект String создается из пула строковых литералов, даже если в пуле уже существует равная строка.

 public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = new String("Hello"); System.out.print(str1 == str2 ); } } 

Выход: false

  • Как заменить специальные символы в строке?
  • Определить кодировку строки в C #
  • Использование strtok () в цикле в C?
  • Bootstrap 4.0 Макет компоновки сетки не работает
  • Как я могу конкатенировать несколько необязательных строк в swift 3.0?
  • Удаление конечного символа новой строки из входа fgets ()
  • Что такое @ перед строкой в ​​C #?
  • Окончание строки - char c = 0 vs char c = '\ 0'
  • Разделить строку в Lua?
  • Почему String.split требует, чтобы разделитель строк был экранирован?
  • заменить специальные символы в строке в java
  • Давайте будем гением компьютера.