Что такое 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 Особенности и усовершенствования

Update: интернированные строки хранятся в основной куче с 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 уже интернированы для вас компилятором. Вы можете интернировать строки самостоятельно, используя метод intern, как показано в предыдущих ответах....


JLS

JLS 7 3.10.5 определяет его и дает практический пример:

кроме того, строковый литерал всегда ссылается на тот же экземпляр класса String. Это потому, что строковые литералы - или, в более общем смысле, строки, которые являются значениями константных выражений (§15.28) - будут "интернированы", чтобы совместно использовать уникальные экземпляры, используя метод String.интерн.

пример 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). В добавление, если строка метода.интерн называется на любой строке, результатом является ссылка на тот же экземпляр класса, который будет возвращен, если эта строка появилась как литерал. Таким образом, следующее выражение должно иметь значение true:

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

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

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

  • в противном случае создается новый экземпляр строки класса, содержащий последовательность кодовых точек Юникода, заданную структурой 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 константа загружается ( литералы)
  • 12: создается новый экземпляр строки (с #2 в качестве аргумента)
  • 35: a и c сравниваются как обычные объекты с if_acmpne

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

  • он имеет специальный CONSTANT_String_info структура, в отличие от обычных объектов (например,new String)
  • структура указывает на CONSTANT_Utf8_info Structure, который содержит данные. Это единственные данные, необходимые для представления строки.

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

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

  • static final String s = "abc" указывает на таблицу констант через ConstantValue Атрибут
  • номера полей не отношу, но все же может быть инициализирован с ldc

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

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


обновление для Java 8 или plus. В Java 8 пространство PermGen (постоянное поколение) удаляется и заменяется мета-пространством. Память пула строк перемещается в кучу JVM.

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

еще одна вещь, вы уже знаете, что при сравнении 2 (ссылки) объектов в Java,'== ' используется для сравнения ссылки на объект,'equals' используется для сравнения содержимого объекта.

давайте проверим этот код:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

результат:

value1 == value2 ---> true

value1 == value3 ---> false

value1.equals(value3) ---> true

value1 == value3.intern() ---> true

вот почему вы должны использовать 'equals ' для сравнения 2 строковых объектов. И вот как intern() полезно.


string interning-это метод оптимизации компилятором. Если у вас есть два одинаковых строковых литерала в одном блоке компиляции, то сгенерированный код гарантирует, что существует только один строковый объект, созданный для всего экземпляра этого литерала(символы, заключенные в двойные кавычки) в сборке.

Я из фона 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:объекты сравниваются по ссылке.

Note2: typeof (int).Имя оценивается методом отражения, поэтому оно не оценивается во время компиляции. вот эти сравнения производятся во время компиляции.

анализ результатов: 1) true, потому что они оба содержат один и тот же литерал, и поэтому сгенерированный код будет иметь только один объект, ссылающийся на "Int32". См. Примечание 1.

2) true, потому что содержимое обоих значений проверяется, которое одинаково.

3) FALSE, потому что str2 и obj не имеют одного и того же литерала. См.примечание 2..