Комментарии (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
При компиляции:
- Компилятор находит все строковые литералы
- Добавляет их в constant pool класса
- 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 код.