← Назад к вопросам
Что такое String.intern()?
1.7 Middle🔥 131 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
String.intern(): Кэширование строк в String Pool
String.intern() — это метод класса String в Java, который возвращает канонический (стандартный) экземпляр строки из String Pool. Если строка с таким значением уже существует в пуле, возвращается ссылка на существующий объект; если нет, текущая строка добавляется в пул и возвращается её ссылка. Это критический метод для оптимизации памяти при работе с большим количеством строк.
Концепция String Pool
Ява хранит строки в специальной области памяти — String Pool (пул строк):
// Строка создается в String Pool
String s1 = "hello"; // Новая строка в пуле
String s2 = "hello"; // Возвращается ТА ЖЕ ссылка из пула
System.out.println(s1 == s2); // true (одинаковые ссылки!)
System.out.println(s1.equals(s2)); // true (одинаковые значения)
Проблема: создание строк вне пула
// Строка создается в heap, не в String Pool
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false (разные объекты!)
System.out.println(s1.equals(s2)); // true (одинаковые значения)
// Проблема: много объектов в памяти с одинаковым значением
Использование String.intern()
Синтаксис
public String intern()
Возвращает тип String — канонический экземпляр строки.
Практический пример
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false (разные объекты)
// Используем intern()
String s1Interned = s1.intern();
String s2Interned = s2.intern();
System.out.println(s1Interned == s2Interned); // true (одна ссылка из пула)
System.out.println(s1Interned == "hello"); // true (совпадает с литералом)
Как работает intern()
public class StringInternExample {
public static void main(String[] args) {
// Шаг 1: Создать строку вне пула
String s = new String("Java");
System.out.println("Создана строка: " + s);
System.out.println("Адрес объекта: " + System.identityHashCode(s));
// Шаг 2: Вызвать intern()
String sInterned = s.intern();
System.out.println("После intern(): " + sInterned);
System.out.println("Адрес объекта: " + System.identityHashCode(sInterned));
// Шаг 3: Сравнить с литералом
String literal = "Java";
System.out.println("Адрес литерала: " + System.identityHashCode(literal));
// Шаг 4: Проверка
System.out.println(sInterned == literal); // true
System.out.println(sInterned.equals(literal)); // true
}
}
Пример: Оптимизация памяти
public class MemoryOptimization {
static class Country {
String name;
String code;
Country(String name, String code) {
// БЕЗ intern(): много дублей в памяти
// this.name = name;
// this.code = code;
// С intern(): экономия памяти
this.name = name.intern();
this.code = code.intern();
}
}
public static void main(String[] args) {
// Много стран с одинаковыми значениями
List<Country> countries = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
countries.add(new Country("Russia", "RU"));
countries.add(new Country("USA", "US"));
countries.add(new Country("China", "CN"));
}
// БЕЗ intern():
// Массив содержит 3 миллиона объектов String
// Памяти потреблено: ~300 MB
// С intern():
// Все одинаковые строки указывают на один объект
// Памяти потреблено: ~1 MB
}
}
Когда intern() полезен
1. Обработка больших наборов данных
public class DataProcessor {
private Set<String> uniqueStrings = new HashSet<>();
public void processLargeDataset(String[] data) {
for (String item : data) {
// Инкавалидировать для экономии памяти
String interned = item.intern();
uniqueStrings.add(interned);
}
}
}
2. Кэширование и идентификация
public class LanguageIdentifier {
private static final Map<String, String> LANGUAGES = new HashMap<>();
static {
// Использовать intern() для ключей
LANGUAGES.put("en".intern(), "English");
LANGUAGES.put("ru".intern(), "Russian");
LANGUAGES.put("zh".intern(), "Chinese");
}
public String getLanguageName(String code) {
// intern() гарантирует правильное совпадение ключей
return LANGUAGES.get(code.intern());
}
}
3. Парсинг и обработка конфигураций
public class ConfigParser {
private Map<String, String> config = new HashMap<>();
public void parseConfig(String configContent) {
String[] lines = configContent.split("\\n");
for (String line : lines) {
if (line.isEmpty()) continue;
String[] parts = line.split("=");
if (parts.length == 2) {
// Использовать intern() для ключей
String key = parts[0].trim().intern();
String value = parts[1].trim();
config.put(key, value);
}
}
}
}
intern() vs equals() vs ==
String s1 = new String("test");
String s2 = new String("test");
String s3 = "test";
String s4 = s1.intern();
// == проверяет ссылки
System.out.println(s1 == s2); // false (разные объекты)
System.out.println(s1 == s3); // false (s1 не в пуле)
System.out.println(s4 == s3); // true (одна ссылка из пула)
// .equals() проверяет значения
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true
System.out.println(s4.equals(s3)); // true
intern() и производительность
Когда intern() помогает
public class GoodInternUsage {
// Много строк с повторяющимися значениями
public void processRepeatedStrings(List<String> data) {
Map<String, Integer> frequency = new HashMap<>();
for (String item : data) {
// intern() экономит память при большом количестве дублей
String interned = item.intern();
frequency.merge(interned, 1, Integer::sum);
}
}
}
Когда intern() вредит производительности
public class BadInternUsage {
// intern() на уникальных строках замедляет код
public void processUniqueStrings(List<String> data) {
Set<String> unique = new HashSet<>();
for (String item : data) {
// ПЛОХО: intern() на уникальных строках
// Добавляет overhead без пользы
unique.add(item.intern());
}
}
}
Контроль String Pool размера
# JVM параметры для управления String Pool
java -XX:StringTableSize=50000 MyApplication
# StringTableSize указывает размер хеш-таблицы String Pool
# По умолчанию: 60013
# Увеличение помогает при работе с миллионами строк
Опасности и ограничения intern()
1. Out of Memory при неправильном использовании
// ОПАСНО: может переполнить String Pool
public void dangerousIntern() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String randomString = "str" + i;
randomString.intern(); // Все строки попадут в пул!
}
// OutOfMemoryError: Java heap space или PermGen space
}
2. String Pool не сборится (в некоторых JVM версиях)
// В Java 7+ String Pool в heap, но все равно нужно быть осторожным
List<String> strings = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
strings.add(("string" + i).intern());
}
// Может привести к высокому использованию памяти
3. Потокобезопасность
// intern() синхронизирован внутри, может быть bottleneck
public void multiThreadedIntern() throws InterruptedException {
Thread[] threads = new Thread[10];
for (int t = 0; t < 10; t++) {
threads[t] = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
String s = "string" + i;
s.intern(); // Конкуренция между потоками
}
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
}
Альтернативы intern()
1. WeakHashMap для кэширования
public class WeakStringCache {
private static final Map<String, String> cache =
Collections.synchronizedMap(new WeakHashMap<>());
public static String intern(String s) {
return cache.computeIfAbsent(s, k -> new String(k));
}
}
2. Guava Intern
import com.google.common.intern.Interners;
public class GuavaIntern {
private static final Interners.WeakInterner<String> interner =
Interners.newWeakInterner();
public String intern(String s) {
return interner.intern(s);
}
}
Лучшие практики
- Используйте intern() только если необходимо — он имеет overhead
- Используйте для повторяющихся значений — когда есть дублирование
- Избегайте на уникальных строках — это замедлит код
- Контролируйте размер String Pool — с помощью XX:StringTableSize
- Профилируйте перед использованием — убедитесь в необходимости
- Рассмотрите альтернативы — WeakHashMap, Guava Interners
- Документируйте причину — почему вы используете intern()
Итоговый пример: Правильное использование
public class OptimizedStringProcessing {
private final Map<String, Integer> countryFrequency = new HashMap<>();
public void processCountries(List<String> countries) {
for (String country : countries) {
// intern() имеет смысл если много дублей
// и мало уникальных значений
String interned = country.intern();
countryFrequency.merge(interned, 1, Integer::sum);
}
}
public Map<String, Integer> getFrequency() {
return new HashMap<>(countryFrequency);
}
}
String.intern() — это мощный, но опасный инструмент для оптимизации памяти. Используйте его умно и только когда это действительно необходимо.