Что такое Java String interning?
Что это Интернировании Строк в Java, когда я должен использовать это, и почему?
5 ответов:
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern()
в основном делает строку.intern () в серии строк гарантирует, что все строки с одинаковым содержимым имеют одинаковую память. Поэтому, если у вас есть список имен, где "Джон" появляется 1000 раз, интернируя вы гарантируете, что только один "Джон" на самом деле выделяется память.
Это может быть полезно для снижения требований к памяти вашей программы. Но имейте в виду, что кэш сохраняется JVM в пуле постоянной памяти, который обычно ограничен по размеру по сравнению с кучей, поэтому вы не должны использовать intern, если у вас не слишком много повторяющихся значений.
подробнее об ограничениях памяти при использовании intern ()
с одной стороны, это правда, что вы можете удалить дубликаты строк с помощью усваивая их. Проблема в том, что интернализованные строки идут постоянная генерация, которая является зарезервированной областью JVM для непользовательских объектов, например Классы, методы и другие внутренние JVM объекты. Размер этой области ограничен, и обычно намного меньше чем куча. Вызов intern() на строку имеет эффект перемещения он выходит из кучи в постоянное поколение, и вы рискуете выбегая из PermGen пространства.
-- От кого: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
от JDK 7 (я имею в виду в HotSpot), что-то имеет измененный.
в JDK 7 интернированные строки больше не выделяются в постоянном поколении кучи Java, а вместо этого выделяются в основной части кучи Java (известной как молодое и старое поколения) вместе с другими объектами, созданными приложением. Это изменение приведет к большему количеству данных, находящихся в основной куче Java, и меньшему количеству данных в постоянном поколении, и таким образом может потребовать изменения размеров кучи. Большинство приложений будет видеть только относительно небольшие различия в использовании кучи из-за этого изменения, но более крупные приложения, которые загружают много классов или интенсивно используют строку.метод intern () будет видеть более существенные различия.
-- от Java SE 7 Особенности и усовершенствования
обновление: интернированные строки хранятся в основной куче с Java 7 и далее. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes
есть некоторые "броские интервью" вопросы, почему вы получаете
String s1 = "testString"; String s2 = "testString"; if(s1 == s2)System.out.println("equals!");
Если вы должны сравнить строки, которые вы должны использовать
equals()
. Выше будет печатать равно, потому чтоtestString
уже интернированы для вас компилятором. Вы можете интернировать строки самостоятельно, используя метод интерна, как показано в предыдущих ответах....
JLS
JLS 7 3.10.5 определяет его и дает практический пример:
кроме того, строковый литерал всегда ссылается на тот же экземпляр класса String. Это вызвано тем, что строковые литералы - или, более широко, строки, которые являются значениями постоянных выражений (§15.28) - "интернированы", чтобы совместно использовать уникальные экземпляры, используя строку метода.интерн.
пример 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 7 5.1 говорит говорит, что интернирование осуществляется волшебно и эффективно с выделенным
CONSTANT_String_info
struct (в отличие от большинства других объектов, которые имеют более общие представления):строковый литерал-это ссылка на экземпляр класса String, и является производным от структуры CONSTANT_String_info (§4.4.3) в двоичном представлении класса или интерфейса. Структура CONSTANT_String_info предоставляет последовательность кодовых точек Юникода, составляющих строковый литерал.
язык программирования Java требует, чтобы идентичные строковые литералы (то есть литералы, содержащие одну и ту же последовательность кодовых точек) должны ссылаться на один и тот же экземпляр класса String (JLS §3.10.5). В кроме того, если метод String.интерн называется на любой строке, результатом является ссылка на тот же экземпляр класса, который будет возвращен, если эта строка появилась как литерал. Таким образом, следующее выражение должно иметь значение true:
("a" + "b" + "c").intern() == "abc"
для получения строкового литерала виртуальная машина Java проверяет последовательность кодовых точек, заданных структурой CONSTANT_String_info.
если метод String.стажер ранее был вызван на экземпляре класса String, содержащем последовательность кодовых точек Unicode, идентичную заданной структурой CONSTANT_String_info, то результатом строкового литерала является ссылка на тот же экземпляр класса String.
в противном случае создается новый экземпляр класса String, содержащий последовательность кодовых точек Юникода, заданную структурой CONSTANT_String_info; ссылка на этот экземпляр класса является результатом строкового литерала вывод. Наконец, вызывается метод intern нового экземпляра 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
константа загружается (the литералы)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 или plus. В Java 8 пространство PermGen (постоянное поколение) удаляется и заменяется Метапространством. Память пула строк перемещается в кучу JVM.
по сравнению с Java 7, размер пула строк увеличивается в куче. Таким образом, у вас есть больше места для внутренних строк, но у вас меньше памяти для всего приложения.
еще одна вещь, вы уже знаете, что при сравнении 2 (ссылки) объектов в Ява,'
==
' используется для сравнения ссылки на объект,'equals
' используется для сравнения содержимого объекта.давайте проверим этот код:
String value1 = "70"; String value2 = "70"; String value3 = new Integer(70).toString();
результат:
value1 == value2
---> правда
value1 == value3
---> false
value1.equals(value3)
---> правда
value1 == value3.intern()
---> правдавот почему вы должны использовать '
equals
' для сравнения 2 строковых объектов. И вот какintern()
полезно.
интернирование строк-это метод оптимизации компилятором. Если у вас есть два одинаковых строковых литерала в одном блоке компиляции, то сгенерированный код гарантирует, что для всех экземпляров этого литерала(символы, заключенные в двойные кавычки) в сборке будет создан только один строковый объект.
Я из фона 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(инт).Имя оценивается по методу отражения, что не получает вычислено во время компиляции. вот эти сравнения производятся во время компиляции.
анализ результатов: 1) true, потому что они оба содержат один и тот же литерал, и поэтому сгенерированный код будет иметь только один объект, ссылающийся на "Int32". См. Примечание 1.
2) true, потому что проверяется содержимое обоих значений, которое является одинаковым.
3) FALSE, потому что str2 и obj не имеют одного и того же литерала. Смотрите примечание 2..