Что такое интерпретация Java String?

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

Ответы

Ответ 1

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern()

В основном выполнение String.intern() в последовательности строк гарантирует, что все строки, имеющие одинаковое содержимое, имеют общую память. Поэтому, если у вас есть список имен, где "john" появляется 1000 раз, путем интернирования вы гарантируете, что на самом деле выделена только одна "john".

Это может быть полезно для уменьшения требований к памяти вашей программы. Но имейте в виду, что кеш поддерживается 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

Обновление: встроенные строки хранятся в основной куче от Java 7 и далее. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ответ 2

Есть несколько вопросов "броского интервью", почему вы получаете

String s1 = "testString";
String s2 = "testString";
if(s1 == s2)System.out.println("equals!");

Если вам нужно сравнить строки, вы должны использовать equals(). Вышеприведенное будет печатать равным, так как testString для вас является компилятором allready интернированным. Вы можете ставить сами строки, используя метод intern, как показано в предыдущих ответах.

Ответ 3

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 (в отличие от большинства других объектов, которые имеют более общие представления):

Строковый литерал является ссылкой на экземпляр класса 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: ldС#2 и та же ldС#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

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

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

Ответ 4

Что такое String intern()?

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

В Java класс String имеет открытый метод intern(), который возвращает каноническое представление для строкового объекта. Класс Javas String частным образом поддерживает пул строк, где строковые литералы автоматически интернируются.

Когда метод intern() вызывается в объекте String, он смотрит строку, содержащуюся в этом объекте String в пуле, если строка найдена там, возвращается строка из пула. В противном случае этот объект String добавляется в пул и возвращается ссылка на этот объект String.

Метод intern() помогает сравнивать два объекта String с оператором ==, просматривая уже существующий пул строковых литералов, он быстрее, чем метод equals(). Пул строк в Java поддерживается для экономии места и для более быстрого сравнения. Я рекомендую использовать equals(), а не ==, для сравнения двух строк. Это связано с тем, что оператор == сравнивает ячейки памяти, а метод equals() сравнивает содержимое, хранящееся в двух объектах.

Почему и когда использовать Intern?

Хотя Java автоматически ставит все строки по умолчанию, помните, что нам нужно только интернировать строки, когда они не являются константами, и мы хотим иметь возможность быстро сравнивать их с другими интернированными строками. Метод intern() следует использовать для строк, построенных с помощью новой String(), чтобы сравнить их с оператором ==.

Давайте рассмотрим следующую программу, чтобы понять поведение intern()

public class TestIntern{
  public static void main(String args[]){
  String s1 = "Hello World";
  String s2 = "Hello World";
  String s3 = new String("Hello World");
  final String s4 = s3.intern();
  //The intern() method returns string from pool, now s4  will be same as s1,s2 
    System.out.println(s1 == s2);// The == operator compares references not values
    System.out.println(s2 == s3);
    System.out.println(s3 == s4);
    System.out.println(s1 == s3);
    System.out.println(s1 == s4);
    System.out.println(s1.equals(s2));
   //The equals() method compares the original content of the string. 
   //It compares values of string for equality
    System.out.println(s2.equals(s3));
    System.out.println(s3.equals(s4));
    System.out.println(s1.equals(s4));
    System.out.println(s1.equals(s3));
}
}
//output
true
false
false
false
true
true
true
true
true
true

Источники = https://en.wikipedia.org/wiki/String_interning

Ответ 5

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

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

Еще одна вещь, вы уже знали, что при сравнении объектов 2 (referrences of) в 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 String объектов. Именно так полезен intern().

Ответ 6

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

Я из фона С#, поэтому я могу объяснить, указав пример:

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) true, потому что оба они содержат один и тот же литерал, и поэтому генерируемый код будет иметь только один объект, ссылающийся на "Int32". См. примечание 1.

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

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