Что такое интернирование строк?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Интернирование строк в 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(), когда:
- Много одинаковых строк в памяти (экономия памяти)
- Сравнение строк через == критично для производительности
- Строки приходят из внешних источников
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() когда:
- Строки уникальны или редко повторяются
- intern() замедлит производительность из-за overhead операции
- 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 при злоупотреблении