Вопрос-ответ

What is the Java string pool and how is "s" different from new String("s")? [duplicate]

Что такое пул строк Java и чем "s" отличается от new String ("s")?

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

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

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

Переведено автоматически
Ответ 1

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


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


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

В качестве интересного примечания, интернирование строк является примером шаблона проектирования с минимальным весом:


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


Ответ 2

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

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

Ответ 3

JLS

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

Соответствующий отрывок из JLS 7 3.10.5:


Более того, строковый литерал всегда ссылается на один и тот же экземпляр класса 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 говорит:


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


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


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

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



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


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



Байт-код

Также полезно взглянуть на реализацию байт-кода в 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."<init>":(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 константа (литералы)

  • 12: создается новый экземпляр string (с #2 в качестве аргумента)

  • 35: a и c сравниваются как обычные объекты с if_acmpne

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


  • он имеет выделенную структуру CONSTANT_String_info, в отличие от обычных объектов (например, new String)

  • struct указывает на структуру CONSTANT_Utf8_info, которая содержит данные. Это единственные необходимые данные для представления строки.

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

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


  • static final String s = "abc" указывает на таблицу констант через атрибут ConstantValue

  • поля, не являющиеся окончательными, не имеют этого атрибута, но все равно могут быть инициализированы с помощью ldc

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

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

Ответ 4

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

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

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

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
========
java string