Что такое пул строк Java и чем "s" отличается от новой строки ("s")?
Что подразумевается под String Pool? И в чем разница между следующими декларациями:
String s = "hello";
String s = new String("hello");
Есть ли разница между хранением этих двух строк JVM?
5 ответов
Пул строк - это конкретная реализация JVM концепции интернирования строк:
В информатике интернирование строк - это метод хранения только одной копии каждого отдельного строкового значения, которое должно быть неизменным. Стажировка строк делает некоторые задачи обработки строк более экономичными по времени или пространству за счет того, что при создании или интернировании строки требуется больше времени. Отдельные значения хранятся в строковом внутреннем пуле.
По сути, внутренний пул строк позволяет среде выполнения экономить память, сохраняя неизменные строки в пуле, так что области приложения могут повторно использовать экземпляры общих строк вместо создания нескольких экземпляров.
В качестве интересного примечания, интернирование строк является примером шаблона проектирования в полутяжелом весе:
Flyweight - это шаблон проектирования программного обеспечения. Flyweight - это объект, который минимизирует использование памяти, делясь как можно большим количеством данных с другими подобными объектами; это способ использовать объекты в большом количестве, когда простое повторное представление будет использовать недопустимый объем памяти.
Пул строк позволяет многократно использовать строковые константы, что возможно потому, что строки в Java являются неизменяемыми. Если вы повторяете одну и ту же строковую константу повсюду в вашем Java-коде, вы на самом деле можете иметь только одну копию этой строки в вашей системе, что является одним из преимуществ этого механизма.
Когда вы используете String s = "string constant";
Вы получаете копию, которая находится в пуле строк. Тем не менее, когда вы делаете String s = new String("string constant");
Вы заставляете копию выделяться.
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
Строковый литерал является ссылкой на экземпляр класса 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 нового экземпляра 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."<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
: создается новый экземпляр строки (с#2
как аргумент)35
:a
а такжеc
сравниваются как обычные объекты сif_acmpne
Представление константных строк довольно волшебно в байт-коде:
- у него есть специальная структура CONSTANT_String_info, в отличие от обычных объектов (например,
new String
) - структура указывает на структуру CONSTANT_Utf8_info, которая содержит данные. Это единственные необходимые данные для представления строки.
и приведенная выше цитата JVMS, кажется, говорит, что всякий раз, когда Utf8, на который указывают ldc
,
Я сделал аналогичные тесты для полей, и:
static final String s = "abc"
указывает на таблицу констант через атрибут ConstantValue- не финальные поля не имеют этого атрибута, но все еще могут быть инициализированы с
ldc
Вывод: есть прямая поддержка байт-кода для пула строк, и представление в памяти эффективно.
Бонус: сравните это с целочисленным пулом, который не имеет прямой поддержки байт-кода (т.е. нет CONSTANT_String_info
аналог).
Строковые объекты в основном являются обертками вокруг строковых литералов. Уникальные строковые объекты объединяются для предотвращения ненужного создания объекта, и JVM может принять решение о внутреннем объединении строковых литералов. Существует также прямая поддержка байт-кода для констант String, на которые ссылаются несколько раз, при условии, что компилятор поддерживает это.
Когда вы используете литерал, скажите 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
========
Удивительно, что никто не ответил прямо на вопрос, но у большинства ответов есть много голосов.
Одним словом, первый создает запись в пуле строк, которую можно использовать повторно (более эффективно благодаря указанным выше ссылкам на неизменность, в основном, интернирование), а второй создает новый объект String (более дорогостоящий).
Оба объекта живут в куче. Ссылки на оба будут в стеке потока.
http://www.journaldev.com/797/what-is-java-string-pool дает четкое представление о том, как это достигается
JVM
выполняет некоторые хитрости при создании экземпляров строковых литералов для увеличения производительности и уменьшения накладных расходов памяти. Чтобы сократить количество объектов String, созданных в JVM, класс String хранит пул строк. Каждый раз, когда ваш код создает строковый литерал, JVM сначала проверяет пул строкового литерала. Если строка уже существует в пуле, возвращается ссылка на объединенный экземпляр. Если строка не существует в пуле, создается новый объект String, а затем помещается в пул.
public class Program
{
public static void main(String[] args)
{
String str1 = "Hello";
String str2 = "Hello";
System.out.print(str1 == str2);
}
}
Вывод: правда
К сожалению, когда вы используете
String a=new String("Hello");
Объект String создается из литерального пула String, даже если в пуле уже существует равная строка.
public class Program
{
public static void main(String[] args)
{
String str1 = "Hello";
String str2 = new String("Hello");
System.out.print(str1 == str2 );
}
}
Выход: ложь