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

Как устроена строка?

1.3 Junior🔥 201 комментариев
#Основы Java

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

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

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

Как устроена строка в Java

String в Java — это одна из самых важных и сложных классов платформы. Понимание её внутреннего устройства критично для оптимизации и правильного использования.

Основная структура String (Java 9+)

public final class String implements Comparable<String>, CharSequence, Serializable {
    private final byte[] value;      // Внутренний массив байтов
    private final byte coder;        // Кодировка: 0 = Latin-1, 1 = UTF-16
    private int hash;                // Кэшированный хеш-код
}

Поля String

1. byte[] value — массив символов

В Java 9+ используется оптимизация Latin-1:

  • Если все символы ASCII (0-127) → 1 байт на символ
  • Если есть Unicode → 2 байта на символ
String s1 = "hello";     // value = [104, 101, 108, 108, 111] (5 байт)
String s2 = "привет";    // value = [1087, 1088, ...] (двойной размер)
String s3 = "hello123";  // value = [104, 101, 108, ...] (8 байт)

2. byte coder — кодировка

public static final byte LATIN1 = 0;   // 1 байт на символ
public static final byte UTF16 = 1;    // 2 байта на символ

3. int hash — кэшированный хеш-код

private int hash;  // 0 = не вычислен

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        // Вычислить только один раз!
        h = isLatin1() ? hashCodeLatin1() : hashCodeUTF16();
        hash = h;
    }
    return h;
}

Первый вызов hashCode() может быть дорогим, но он кэшируется.

Как создаётся String

1. String Literal (из кода)

String s = "hello";  // String pool

При компиляции:

  1. Компилятор находит все строковые литералы
  2. Добавляет их в constant pool класса
  3. JVM при загрузке класса добавляет их в String pool

2. new String()

String s1 = new String("hello");   // Новый объект на HEAP
String s2 = "hello";              // Из String pool

s1 == s2  // false (разные объекты)
s1.equals(s2)  // true (одинаковое содержимое)

3. String.intern()

String s1 = new String("hello");
String s2 = s1.intern();  // Вернёт версию из pool

s2 == "hello"  // true (теперь из pool)

String Pool (интернирование)

String pool — это специальная кэш-таблица (HashMap) для оптимизации памяти:

String s1 = "hello";
String s2 = "hello";
s1 == s2  // true! Один и тот же объект

// JVM internal:
private static class StringPool {
    private static final Map<String, String> pool = new HashMap<>();
    
    public static String intern(String s) {
        String result = pool.get(s);
        if (result == null) {
            result = s;
            pool.put(s, result);
        }
        return result;
    }
}

Immutability (неизменяемость)

String FINAL и immutable:

public final class String {  // ← final класс!
    private final byte[] value;  // ← final поле!
}

// Результат:
String s = "hello";
s.substring(0, 2);  // Вернёт новый объект "he"
String s2 = s;      // s и s2 указывают на один объект
// s2 никогда не изменится, потому что String immutable

Последствия Immutability

Преимущества:

  • Thread-safe без синхронизации
  • Можно использовать как ключ в HashMap
  • Безопасно передавать между потоками
  • Кэширование hashCode()
// ✅ Безопасно
Map<String, Value> map = new HashMap<>();
map.put("key", value);
// Key никогда не изменится

Недостатки:

  • Конкатенация строк создаёт новые объекты
// ❌ Плохо: создаёт 100 новых объектов
String result = "";
for (int i = 0; i < 100; i++) {
    result = result + i;  // Новый String каждый раз
}

// ✅ Хорошо: используй StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i);
}
String result = sb.toString();  // Один String в конце

Сравнение String создания

String s1 = "hello";
String s2 = "hel" + "lo";     // Компилятор объединит → pool
String s3 = "he" + "l" + "lo"; // Компилятор объединит → pool
String s4 = new String("hello"); // Новый объект!

String a = "hel";
String s5 = a + "lo";           // Runtime конкатенация → новый объект

s1 == s2  // true (оба из pool)
s1 == s3  // true (оба из pool)
s1 == s4  // false (s4 новый объект)
s1 == s5  // false (runtime конкатенация)

Внутренняя структура памяти

String object на HEAP:
┌─────────────────────────┐
│ Object Header (12-16B)  │  JVM overhead
├─────────────────────────┤
│ byte[] value (8B)       │  Ссылка на массив
├─────────────────────────┤
│ byte coder (1B)         │  0 = Latin-1, 1 = UTF-16
├─────────────────────────┤
│ int hash (4B)           │  Кэшированный hashCode
├─────────────────────────┤
│ padding (3B)            │  Выравнивание
└─────────────────────────┘
Общий размер: ~32 байта на объект

Массив byte[] values на HEAP:
┌─────────────────────────┐
│ Array Header (12-16B)   │  JVM overhead
├─────────────────────────┤
│ length (4B)             │  Длина массива
├─────────────────────────┤
│ byte[0..n]              │  Сами данные (1 или 2 байта на символ)
└─────────────────────────┘

Для строки "hello":

  • String object: ~32B
  • byte[] array: 16B + 5B = 21B (Latin-1)
  • Total: ~53B

Для строки "привет":

  • String object: ~32B
  • byte[] array: 16B + 12B = 28B (UTF-16)
  • Total: ~60B

Методы String

Важные операции:

String s = "hello world";

// Поиск и подстроки (создают новые объекты)
s.charAt(0);              // 'h'
s.substring(0, 5);        // "hello" (новый String)
s.indexOf('o');           // 4 (первое вхождение)
s.lastIndexOf('o');       // 7 (последнее вхождение)

// Преобразование
s.toUpperCase();          // "HELLO WORLD" (новый String)
s.toLowerCase();          // "hello world" (новый String)
s.replace('l', 'L');      // "heLLo worLd" (новый String)

// Разбор
s.split(" ");             // ["hello", "world"]
s.startsWith("hello");    // true
s.endsWith("world");      // true
s.contains("lo wor");     // true

// Информация
s.length();               // 11
s.isEmpty();              // false
s.getBytes();             // byte[] (при других кодировках)

StringBuilder vs StringBuffer vs String

// String — неизменяемая
String s = "hello";
s = s + " world";  // Создаёт новый String объект

// StringBuilder — изменяемая (НЕ thread-safe)
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
String result = sb.toString();
// Создаёт только 1 String в конце

// StringBuffer — изменяемая (thread-safe)
StringBuffer sf = new StringBuffer("hello");
sf.append(" world");
String result = sf.toString();
// Медленнее чем StringBuilder

Performance сравнение:

// ❌ Очень медленно: 100 String объектов
String result = "";
for (int i = 0; i < 100; i++) {
    result = result + i;  // O(n^2)
}

// ✅ Быстро: 1 объект
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i);
}
String result = sb.toString();  // O(n)

Хеширование String

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        byte[] val = value;
        for (int i = 0; i < val.length; i++) {
            h = 31 * h + (val[i] & 0xff);
        }
        hash = h;
    }
    return h;
}

// Пример:
"abc".hashCode()
// h = 31 * 0 + 'a' = 97
// h = 31 * 97 + 'b' = 3104
// h = 31 * 3104 + 'c' = 96354

Почему 31? Это нечётное простое число — хорошо для распределения хеша.

Оптимизация String

1. Используй String literals для известных значений

// ✅ Хорошо
String s1 = "hello";  // pool
String s2 = "hello";  // тот же объект

// ❌ Плохо
String s1 = new String("hello");  // новый объект
String s2 = new String("hello");  // ещё новый объект

2. Используй StringBuilder для конкатенации

// ❌ O(n^2)
String result = "";
for (String item : items) {
    result += item;
}

// ✅ O(n)
StringBuilder sb = new StringBuilder();
for (String item : items) {
    sb.append(item);
}
String result = sb.toString();

3. Кэшируй hashCode для часто используемых строк

public class CachedString {
    private final String value;
    private final int cachedHash;
    
    public CachedString(String value) {
        this.value = value;
        this.cachedHash = value.hashCode();  // Кэшируй один раз
    }
}

Резюме

String в Java:
  • Неизменяемая (immutable)
  • Хранит байты (Latin-1 или UTF-16)
  • Кэширует hashCode()
  • Интернируется для оптимизации памяти
  • Thread-safe без синхронизации
  • Конкатенация создаёт новые объекты (используй StringBuilder)
  • Хеширование вычисляется один раз и кэшируется

Понимание внутреннего устройства String помогает написать эффективный и безопасный Java код.

Как устроена строка? | PrepBro