Что такое Java String interning?


Что это Интернировании Строк в Java, когда я должен использовать это, и почему?

5 171

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..