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

Почему StringBuilder не создает новых строк в String Pool?

2.3 Middle🔥 151 комментариев
#Другое

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

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

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

Почему StringBuilder не создает новых строк в String Pool?

Этот вопрос про архитектуру String internement в Java и как StringBuilder это обходит для оптимизации.

String Pool — что это

String Pool — это специальное хранилище в памяти JVM для уникальных строк:

String s1 = "Hello";     // Создаёт в String Pool
String s2 = "Hello";     // Берёт из Pool (s1 == s2)
String s3 = new String("Hello"); // Создаёт в Heap, НЕ в Pool

Визуально:

String Pool (в PermGen/MetaSpace):
["Hello"] ← s1, s2 указывают сюда
["World"]
["Java"]

Heap:
[new String("Hello")] ← s3 указывает сюда

String Pool экономит память — одна строка, несколько ссылок.

Почему String немутабельна

Strings в Java immutable:

String s = "Hello";
s = s + " World";  // НЕ меняет старую строку!
               // Создаёт НОВУЮ строку

Визуально:

ДО: s → String Pool["Hello"]

ПОСЛЕ: s → Heap["Hello World"] (новая!)

Проблема: каждое + создаёт новую строку в памяти!

StringBuilder — решение

StringBuilder — это mutable контейнер для строк:

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // Одна новая строка

Визуально:

StringBuilder (в Heap)
┌─────────────────────────────┐
│ [H][e][l][l][o][ ][W][o][r][l][d] │
│ Изменяемый массив chars      │
└─────────────────────────────┘
         ↓ toString()
String Pool или Heap
   ["Hello World"] (одна новая)

Почему StringBuilder НЕ использует String Pool

Ключевая причина: StringBuilder создаёт NEW String каждый раз

StringBuilder sb = new StringBuilder();
sb.append("Hello");
String s1 = sb.toString();  // Новая String в Heap
String s2 = sb.toString();  // ДРУГАЯ новая String в Heap

// s1 == s2 ? НЕТ (разные объекты)
// s1.equals(s2) ? ДА (одинаковый контент)

Почему так:

  1. StringBuilder.toString() создаёт непосредственно в Heap (новый объект)
  2. НЕ вызывает String.intern() — который добавляет в Pool
  3. Для производительности — intern() медленный (требует поиска в Pool)

Реализация StringBuilder.toString()

// Упрощённая реализация
public final class StringBuilder {
    private char[] value;  // Изменяемый массив
    private int count;     // Текущая длина
    
    public String toString() {
        return new String(value, 0, count);
        // НЕ intern()!
        // Создаёт новую String в Heap напрямую
    }
}

Сравним со String.intern():

StringBuilder sb = new StringBuilder("Hello");
String s1 = sb.toString();  // Heap (новая каждый раз)
String s2 = sb.toString();

System.out.println(s1 == s2);  // false (разные адреса)
System.out.println(s1.intern() == s2.intern()); // true (Pool)

Пример: где это критично

Медленно — постоянное создание:

String result = "";
for (int i = 0; i < 1000; i++) {
    result += "item" + i;  // Создаёт 1000 новых String!
}
// Каждое += : 
//   1. new String(result + "item" + i)
//   2. старая выбрасывается
// = Garbage Collection ад!

Быстро — StringBuilder:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("item").append(i);
}
String result = sb.toString();  // Одна новая String
// Только 1 новая String в конце!

Бенчмарк:

Способ          | Время    | Новых String
─────────────────|──────────|-────────────
string +=       | 5000 ms  | 1000
StringBuilder   | 10 ms    | 1
String.join()   | 20 ms    | 1

Почему StringBuilder НЕ использует intern()

Если бы StringBuilder вызывал intern():

// Гипотетически (медленно!)
public String toString() {
    String newStr = new String(value, 0, count);
    return newStr.intern();  // Ищет в Pool
}

Проблемы:

  1. Поиск в Pool — O(n) или O(log n) — медленно
  2. Конкуренция потоков — Pool synchronized
  3. Переполнение Pool — каждая строка = entry в Pool
  4. Память неэффективно — String Pool в MetaSpace (лимит!)

Философия Java:

  • ✅ String literals → Pool (известны в compile-time)
  • ✅ String.intern() → пользователь решает
  • ❌ StringBuilder.toString() → НЕ надо заполнять Pool

Когда НУЖНО intern()

// Если часто сравниваешь по адресу (==)
String s1 = getStringFromDatabase();  // Heap
String s2 = getStringFromDatabase();  // Heap

if (s1 == s2) {  // false! Разные объекты
    // ...
}

if (s1.intern() == s2.intern()) {  // true! Pool
    // ...
}

// Или лучше так:
if (s1.equals(s2)) {  // true! Сравниваем контент
    // ...
}

Визуальное сравнение

String literals (в Pool):

String s1 = "Hello";
String s2 = "Hello";

Pool: ["Hello"] ← s1, s2 (один объект)

s1 == s2 → true

StringBuilder.toString() (в Heap):

StringBuilder sb = new StringBuilder("Hello");
String s1 = sb.toString();
String s2 = sb.toString();

Heap: ["Hello"]s1  ["Hello"]s2 (разные объекты)

s1 == s2 → false
s1.equals(s2) → true

Best Practices

Используй StringBuilder для:

// Много append операций
StringBuilder sb = new StringBuilder();
for (String item : items) {
    sb.append(item).append(", ");
}
String result = sb.toString();

// SQL запросы
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM users WHERE ");
query.append("age > ").append(age);
query.append(" AND status = '").append(status).append("'");

Используй String для:

// Строки в литералах (автоматически в Pool)
String greeting = "Hello World";

// Одноразовое использование
String result = str1 + str2;

// Функциональные потоки
String combined = Stream.of(a, b, c)
    .collect(Collectors.joining(", "));

Не делай:

// Неправильно — StringBuilder в loop
String result = "";
for (String item : items) {
    result += item;  // O(n²)
}

// Правильно
StringBuilder sb = new StringBuilder();
for (String item : items) {
    sb.append(item);  // O(n)
}
result = sb.toString();

Вывод

StringBuilder НЕ создаёт в String Pool потому:

  1. StringBuilder.toString() вызывает new String() — создаёт в Heap
  2. НЕ вызывает String.intern() — для производительности
  3. String Pool для literals — известных в compile-time
  4. Heap для динамических String — быстрее и гибче

Правило:

  • String literals ("Hello") → Pool
  • StringBuilder результаты → Heap
  • String.intern() → пользователь решает

Это оптимальный баланс между памятью и производительностью.