← Назад к вопросам

Что такое 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);
    }
}

Лучшие практики

  1. Используйте intern() только если необходимо — он имеет overhead
  2. Используйте для повторяющихся значений — когда есть дублирование
  3. Избегайте на уникальных строках — это замедлит код
  4. Контролируйте размер String Pool — с помощью XX:StringTableSize
  5. Профилируйте перед использованием — убедитесь в необходимости
  6. Рассмотрите альтернативы — WeakHashMap, Guava Interners
  7. Документируйте причину — почему вы используете 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() — это мощный, но опасный инструмент для оптимизации памяти. Используйте его умно и только когда это действительно необходимо.