Вопрос-ответ

What is Java String interning?

Что такое интернирование строк Java?

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

Есть несколько "цепляющих вопросов из интервью", например, почему вы получаете equals! если вы выполните приведенный ниже фрагмент кода.

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

Если вы хотите сравнить строки, вам следует использовать equals(). Выше будет выведено равно, потому что testString уже интернировано для вас компилятором. Вы можете интернировать строки самостоятельно, используя метод 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.



Байт-код

Давайте декомпилируем некоторый байт-код 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: создается новый экземпляр string (с #2 в качестве аргумента)

  • 35: a и c сравниваются как обычные объекты с if_acmpne

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


  • он имеет специальную структуру CONSTANT_String_info, в отличие от обычных объектов (например, new String)

  • struct указывает на структуру CONSTANT_Utf8_info, которая содержит данные. Это единственные необходимые данные для представления строки.

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

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


  • static final String s = "abc" указывает на таблицу констант через атрибут ConstantValue

  • поля, не являющиеся конечными, не имеют этого атрибута, но все равно могут быть инициализированы с помощью ldc

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

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

Ответ 4

Обновление для Java 8+

В 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' для сравнения двух строковых объектов. И вот как intern() это полезно.

java string