Что такое интернирование Java String?
Что такое String Interning в Java, когда я должен его использовать и почему?
8 ответов
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html
По сути, выполнение String.intern() для ряда строк гарантирует, что все строки, имеющие одинаковое содержимое, совместно используют одну и ту же память. Таким образом, если у вас есть список имен, где "Джон" появляется 1000 раз, при интернировании вы гарантируете, что только один "Джон" фактически выделен памяти.
Это может быть полезно для уменьшения требований к памяти вашей программы. Но имейте в виду, что JVM поддерживает кеш в постоянном пуле памяти, размер которого обычно ограничен по сравнению с кучей, поэтому не следует использовать intern, если у вас не слишком много повторяющихся значений.
Подробнее об ограничениях памяти при использовании intern()
С одной стороны, это правда, что вы можете удалить дубликаты строк, усвоив их. Проблема заключается в том, что интернализованные строки отправляются в постоянную генерацию, которая является областью JVM, зарезервированной для не пользовательских объектов, таких как классы, методы и другие внутренние объекты JVM. Размер этой области ограничен и обычно намного меньше, чем куча. Вызов intern() для String приводит к его перемещению из кучи в постоянное поколение, и вы рискуете исчерпать пространство PermGen.
- От: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
С JDK 7 (я имею в виду в HotSpot) что-то изменилось.
В JDK 7 интернированные строки больше не выделяются в постоянном поколении кучи Java, а вместо этого выделяются в основной части кучи Java (известной как молодое и старое поколения) вместе с другими объектами, созданными приложением, Это изменение приведет к увеличению объема данных, находящихся в основной куче Java, и уменьшению объема данных в постоянной генерации, что может потребовать корректировки размеров кучи. Большинство приложений увидят только относительно небольшие различия в использовании кучи из-за этого изменения, но более крупные приложения, которые загружают много классов или интенсивно используют метод String.intern(), увидят более существенные различия.
- Из Java SE 7 Особенности и улучшения
Обновление: строки Interned хранятся в основной куче начиная с Java 7 и далее. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
Есть несколько "броских интервью" вопросов, почему Вы получаете
String s1 = "testString";
String s2 = "testString";
if(s1 == s2)System.out.println("equals!");
Если вы должны сравнить строки, вы должны использовать equals()
, Выше будет печатать равно, потому что testString
уже интернирован для вас компилятором. Вы можете интернировать строки самостоятельно, используя метод intern, как показано в предыдущих ответах....
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 говорит, что интернирование реализовано волшебно и эффективно с выделенным CONSTANT_String_info
struct (в отличие от большинства других объектов, которые имеют более общие представления):
Строковый литерал является ссылкой на экземпляр класса 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
аналог).
Обновление для Java 8 или плюс. В Java 8 пространство PermGen (Permanent Generation) удалено и заменено метапространством. Память пула строк перемещается в кучу JVM.
По сравнению с Java 7 размер пула строк увеличен в куче. Следовательно, у вас есть больше места для внутренних строк, но у вас меньше памяти для всего приложения.
Еще одна вещь, вы уже знали, что при сравнении 2 (ссылок) объектов в Java, '==
'используется для сравнения ссылки на объект'equals
'используется для сравнения содержимого объекта.
Давайте проверим этот код:
String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();
Результат:
value1 == value2
---> правда
value1 == value3
---> ложь
value1.equals(value3)
---> правда
value1 == value3.intern()
---> правда
Вот почему вы должны использоватьequals
сравнить 2 объекта String. И вот как intern()
Полезно.
В книге OCP Java SE 11 Programmer Deshmukh я нашел самое простое объяснение интернирования, которое выглядело следующим образом: поскольку строки являются объектами и поскольку все объекты в Java всегда хранятся только в пространстве кучи, все строки хранятся в пространстве кучи. Однако Java хранит строки, созданные без использования ключевого слова new, в специальной области кучи, которая называется "пулом строк". Java сохраняет строки, созданные с использованием ключевого слова new, в обычном пространстве кучи.
Назначение пула строк - поддерживать набор уникальных строк. Каждый раз, когда вы создаете новую строку без использования ключевого слова new, Java проверяет, существует ли уже такая строка в пуле строк. Если это так, Java возвращает ссылку на тот же объект String, а если нет, Java создает новый объект String в пуле строк и возвращает ссылку на него. Так, например, если вы дважды используете строку "hello" в своем коде, как показано ниже, вы получите ссылку на ту же строку. Мы действительно можем проверить эту теорию, сравнив две разные ссылочные переменные с помощью оператора ==, как показано в следующем коде:
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false
Оператор == просто проверяет, указывают ли две ссылки на один и тот же объект или нет, и возвращает истину, если это так. В приведенном выше коде str2 получает ссылку на тот же объект String, который был создан ранее. Однако str3 и str4 получают ссылки на два совершенно разных объекта String. Вот почему str1 == str2 возвращает true, а str1 == str3 и str3 == str4 возвращают false . Фактически, когда вы выполняете new String("привет"); два объекта String создаются вместо одного, если это первый раз, когда строка "hello" используется в любом месте программы - один в пуле строк из-за использования строки в кавычках, а другой в обычном пространстве кучи, потому что использования нового ключевого слова.
Пул строк - это способ Java сэкономить программную память, избегая создания нескольких объектов String, содержащих одно и то же значение. Можно получить строку из пула строк для строки, созданной с использованием ключевого слова new, с помощью метода intern String. Это называется "интернированием" строковых объектов. Например,
String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj
System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Строковое интернирование - это метод оптимизации компилятором. Если у вас есть два одинаковых строковых литерала в одном модуле компиляции, то сгенерированный код гарантирует, что для всего экземпляра этого литерала (символы, заключенные в двойные кавычки) внутри сборки создан только один строковый объект.
Я из C# фона, поэтому я могу объяснить, приведя пример из этого:
object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
вывод следующих сравнений:
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?
Примечание 1: объекты сравниваются по ссылке.
Примечание 2: typeof (int). Имя оценивается методом отражения, поэтому оно не оценивается во время компиляции. Вот эти сравнения сделаны во время компиляции.
Анализ результатов:1) истина, потому что они оба содержат один и тот же литерал, и поэтому сгенерированный код будет иметь только один объект, ссылающийся на "Int32". Смотрите примечание 1.
2) истина, потому что проверяется содержимое обоих значений, что является одинаковым.
3) ЛОЖЬ, потому что str2 и obj не имеют одинаковые литералы. Смотрите примечание 2.
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references
for eg: String s1=new String("abc");
String s2="abc";
String s3="abc";
s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true
now if we do intern on s1
s1=s1.intern()
//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value “abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.
Используя ссылку на объект кучи, если мы хотим получить соответствующую ссылку на объект SCP, мы должны использовать метод intern().
Пример :
class InternDemo
{
public static void main(String[] args)
{
String s1=new String("smith");
String s2=s1.intern();
String s3="smith";
System.out.println(s2==s3);//true
}
}