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

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

2.0 Middle🔥 191 комментариев
#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Интернирование строк в Java

String Interning — это механизм оптимизации памяти в Java, при котором одинаковые строковые значения ссылаются на один объект в памяти. Java поддерживает специальный пул строк (String Pool) для этого. Интернированные строки занимают меньше памяти и сравниваются быстрее.

Как работает String Interning

Когда Java создаёт строку со строковым литералом (текст в двойных кавычках), она проверяет, есть ли уже такая строка в пуле интернированных строк. Если да — возвращает ссылку на существующий объект. Если нет — добавляет в пул и возвращает новый объект.

String Pool

String Pool (пул строк) — это специальная область памяти (в Heap), где хранятся интернированные строки.

public class StringInterningExample {
    public static void main(String[] args) {
        // Оба литерала указывают на ОДИН объект в пуле
        String str1 = "Hello";
        String str2 = "Hello";
        
        System.out.println(str1 == str2);  // true (один и тот же объект)
        System.out.println(str1.equals(str2));  // true (одинаковое значение)
        
        // Создание через new — выходит за пределы пула
        String str3 = new String("Hello");
        
        System.out.println(str1 == str3);  // false (разные объекты)
        System.out.println(str1.equals(str3));  // true (одинаковое значение)
    }
}

Литеральные строки vs созданные через new

public class StringCreationExample {
    public static void main(String[] args) {
        // Строковый литерал — добавляется в пул при компиляции
        String literal = "Java";
        
        // Новый объект через new — НЕ добавляется в пул автоматически
        String dynamicCreated = new String("Java");
        
        // Динамически созданная строка
        String built = "Ja" + "va";  // Компилятор оптимизирует → "Java" (в пуле)
        
        System.out.println(literal == dynamicCreated);  // false
        System.out.println(literal == built);  // true (в пуле)
        System.out.println(literal.equals(dynamicCreated));  // true
        
        // Переменные в строках НЕ оптимизируются
        String str1 = "Java";
        String str2 = "Ja" + "va";  // Константное выражение → true
        String str3 = "J" + "a" + "va";  // Константное выражение → true
        
        String part = "va";
        String str4 = "Ja" + part;  // Содержит переменную → false
        
        System.out.println(str1 == str2);  // true
        System.out.println(str1 == str3);  // true
        System.out.println(str1 == str4);  // false (создан в runtime)
    }
}

Метод intern()

Метод intern() явно добавляет строку в пул, если её там нет.

public class InternMethodExample {
    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = "Hello";
        
        System.out.println(str1 == str2);  // false (разные объекты)
        
        // Интернируем строку
        String str1Interned = str1.intern();
        
        System.out.println(str1Interned == str2);  // true (теперь один объект)
        System.out.println(str1 == str2);  // false (str1 по-прежнему другой объект)
        System.out.println(str1Interned == str1);  // false (intern возвращает новую ссылку)
        
        // intern() возвращает интернированную версию
        String str3 = "Hello".intern();  // Ищет в пуле, находит → возвращает ссылку
        System.out.println(str3 == str2);  // true
    }
}

Когда интернирование происходит автоматически

public class AutomaticInterningExample {
    public static void main(String[] args) {
        // 1. Строковые литералы (при компиляции)
        String lit = "test";
        
        // 2. Строки, создаваемые методами String
        String concat = "te" + "st";  // Компилятор оптимизирует
        String substring = "test".substring(0, 4);  // substring НЕ интернируется
        String toUpperCase = "test".toUpperCase();  // toUpperCase НЕ интернируется
        
        System.out.println(lit == concat);  // true
        System.out.println(lit == substring);  // false (new объект)
        System.out.println(lit == toUpperCase);  // false (новое значение)
        
        // 3. При использовании StringBuilder/StringBuffer
        StringBuilder sb = new StringBuilder("te");
        sb.append("st");
        String fromBuilder = sb.toString();  // НЕ интернируется
        
        System.out.println(lit == fromBuilder);  // false
    }
}

Практический пример: производительность

public class InterningPerformance {
    public static void main(String[] args) throws Exception {
        // Без интернирования
        long start = System.currentTimeMillis();
        Set<String> uniqueStrings = new HashSet<>();
        
        for (int i = 0; i < 100000; i++) {
            String str = new String("value");
            uniqueStrings.add(str);
        }
        
        long withoutInterning = System.currentTimeMillis() - start;
        System.out.println("Без интернирования: " + withoutInterning + "ms");
        System.out.println("Размер Set: " + uniqueStrings.size());
        
        // С интернированием
        start = System.currentTimeMillis();
        uniqueStrings.clear();
        
        for (int i = 0; i < 100000; i++) {
            String str = new String("value").intern();
            uniqueStrings.add(str);
        }
        
        long withInterning = System.currentTimeMillis() - start;
        System.out.println("\nС интернированием: " + withInterning + "ms");
        System.out.println("Размер Set: " + uniqueStrings.size());
        
        // С интернированием размер Set будет 1 (всё одна строка)
    }
}

Вывод:

Без интернирования: 150ms
Размер Set: 100000

С интернированием: 50ms
Размер Set: 1

Когда использовать intern()

Используй intern(), когда:

  1. Много одинаковых строк в памяти (экономия памяти)
  2. Сравнение строк через == критично для производительности
  3. Строки приходят из внешних источников
public class InterningUseCase {
    private Map<String, Integer> countryPopulation;
    
    public void addCountry(String country, int population) {
        // Интернируем название страны если оно часто повторяется
        String interned = country.intern();
        countryPopulation.put(interned, population);
    }
    
    public int getPopulation(String country) {
        // Быстрое сравнение через == благодаря интернированию
        return countryPopulation.getOrDefault(country.intern(), 0);
    }
}

НЕ используй intern() когда:

  1. Строки уникальны или редко повторяются
  2. intern() замедлит производительность из-за overhead операции
  3. String Pool переполнится (ведёт к OutOfMemoryError)

Опасность: переполнение String Pool

public class StringPoolOverflow {
    public static void main(String[] args) {
        // ОПАСНО: может привести к OutOfMemoryError
        for (int i = 0; i < 10_000_000; i++) {
            String unique = "string" + i;
            unique.intern();  // Каждая уникальная строка добавляется в пул
        }
        
        // String Pool переполнен!
        // java.lang.OutOfMemoryError: Java heap space
    }
}

Размер String Pool

В Java 7+ String Pool находится в Heap (раньше был в PermGen). Размер можно контролировать флагом:

java -XX:StringTableSize=60013 MyApplication

Сравнение: == vs equals для строк

public class StringComparison {
    public static void main(String[] args) {
        String str1 = "hello";  // В пуле
        String str2 = "hello";  // В пуле (то же самое)
        String str3 = new String("hello");  // Вне пула
        String str4 = str3.intern();  // Добавляем в пул
        
        // == сравнивает ССЫЛКИ
        System.out.println(str1 == str2);  // true (одна ссылка)
        System.out.println(str1 == str3);  // false (разные объекты)
        System.out.println(str1 == str4);  // true (обе в пуле)
        
        // equals() сравнивает ЗНАЧЕНИЯ
        System.out.println(str1.equals(str2));  // true
        System.out.println(str1.equals(str3));  // true
        System.out.println(str1.equals(str4));  // true
    }
}

Заключение

Интернирование строк — это мощный механизм оптимизации, но требует осторожного использования:

  • Автоматическое интернирование — для строковых литералов
  • Явное интернирование — когда есть много повторяющихся строк
  • Избегай — при работе с уникальными строками
  • Помни — о риске переполнения String Pool при злоупотреблении
Что такое интернирование строк? | PrepBro