Слышал ли что-то про метод intern?
Метод intern() в Java — это метод класса String, предназначенный для работы с пулом строк (string pool), то есть специальным хранилищем уникальных строковых литералов в памяти. Он позволяет уменьшать количество объектов в памяти, ускорять сравнение строк и оптимизировать использование памяти в случаях, где часто создаются одинаковые строки.
Общая характеристика метода
Сигнатура:
public String intern()
Описание:
Метод intern() возвращает каноническое представление строки — то есть единственный экземпляр строки, содержащей те же символы, что и исходная, из пула строк. Если такая строка уже есть в пуле, она возвращается. Если её там нет, строка добавляется в пул и возвращается ссылка на неё.
Что такое пул строк (string pool)
Пул строк — это специальная область в памяти, управляемая JVM, где хранятся уникальные строковые литералы. Это означает, что строка "abc", встречающаяся несколько раз в коде как литерал, будет представлена одним и тем же объектом в памяти.
JVM создаёт строки в пуле:
-
автоматически для всех строковых литералов, таких как "hello";
-
вручную — при использовании метода intern().
Поведение метода intern()
Рассмотрим пример:
String a = "hello";
String b = new String("hello");
System.out.println(a == b); // false — разные объекты
System.out.println(a.equals(b)); // true — одинаковое содержимое
String c = b.intern();
System.out.println(a == c); // true — теперь обе строки ссылаются на один объект из пула
Разбор:
-
a — строковый литерал, автоматически попадает в пул;
-
b — новый объект в куче (heap), не из пула;
-
b.intern() возвращает строку "hello" из пула, поэтому c == a.
Когда используется intern()
-
Оптимизация памяти, особенно когда:
-
создаётся много одинаковых строк (например, при разборе XML, JSON, CSV);
-
строки часто сравниваются с использованием == (что быстрее, чем equals()).
-
-
Повышение производительности при:
-
большом количестве операций сравнения строк;
-
использовании хеш-структур, где == предпочтительнее.
-
-
Ручное управление пулом строк, если строки создаются динамически (например, на входе из базы данных или файлов).
Недостатки и предостережения
-
intern() не всегда улучшает производительность. При неправильном использовании он может нагрузить сборщик мусора или заполнить Metaspace/PermGen.
-
В Java 6 строки из пула хранились в PermGen (ограниченный по размеру), а в Java 7+ — в Heap, что улучшило ситуацию.
-
Частое использование intern() на уникальных строках может привести к переполнению памяти или уменьшению скорости, так как каждая строка будет сохраняться навсегда (до завершения программы или выгрузки класса).
-
Сравнение == после intern() безопасно только если точно известно, что обе строки прошли через пул.
Пример: поведение без и с intern()
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false — два разных объекта
System.out.println(s1.intern() == s2.intern()); // true — одна строка в пуле
В этом примере создаются две разные строки, но после применения intern() обе переменные будут ссылаться на одну строку в пуле.
intern() и компиляция литералов
Когда используется строковый литерал (например, "abc"), компилятор автоматически помещает его в пул:
String a = "abc";
String b = "abc";
System.out.println(a == b); // true
Однако при создании строки через new String("abc"), объект попадает в кучу:
String c = new String("abc");
System.out.println(a == c); // false
Только после c.intern() переменная будет ссылаться на строку "abc" из пула.
intern() в Java 6 и Java 7+
В Java 6 пул строк находился в PermGen (Permanent Generation) — специальной области памяти JVM с ограниченным размером. Это ограничение приводило к OutOfMemoryError при активном использовании intern() с большим количеством уникальных строк.
В Java 7 и выше пул строк перенесли в Heap (кучу), и лимит теперь зависит от общего объема памяти JVM, а не от отдельно выделенного PermGen, который был полностью удалён в Java 8 (заменён на Metaspace). Это сделало использование intern() более безопасным.
intern() и String.intern() как оптимизация кэширования
В некоторых случаях метод intern() может использоваться как замена кэшу строк, например:
Map<String, Integer> userCache = new HashMap<>();
for (String line : fileLines) {
String userId = line.split(",")\[0\].intern(); // убирает дубликаты userId
userCache.put(userId, ...);
}
В этом примере intern() экономит память, если userId часто повторяется.
Тест на производительность
В сценариях, где часто создаются дублирующиеся строки:
for (int i = 0; i < 1000000; i++) {
String s = new String("ABCDEF").intern();
}
Здесь создаётся миллион новых строк, но после интернирования в памяти будет существовать только одна строка "ABCDEF" в пуле.
Важные замечания
-
Метод intern() полезен, но должен использоваться обдуманно.
-
Лучше использовать его там, где много дублирующихся строк и часто выполняется сравнение строк.
-
Не рекомендуется применять к строкам, которые гарантированно уникальны (например, случайные UUID), так как это не даст выигрыша в памяти, но увеличит нагрузку на пул.