Як працює метод split()?
Метод split() розбиває рядок на масив підрядків за заданим роздільником.
🟢 Junior Level
Метод split() розбиває рядок на масив підрядків за заданим роздільником.
Простий приклад:
String data = "apple,banana,cherry";
String[] fruits = data.split(",");
// ["apple", "banana", "cherry"]
Важливо: Роздільник — це регулярний вираз, а не просто рядок! Деякі символи потрібно екранувати:
String ip = "192.168.1.1";
String[] parts = ip.split("\\."); // Крапку потрібно екранувати!
// ["192", "168", "1", "1"]
Порожні рядки в кінці за замовчуванням видаляються:
"a,b,,,".split(","); // ["a", "b"] — порожні прибрані
"a,b,,,".split(",", -1); // ["a", "b", "", "", ""] — всі збережені
🟡 Middle Level
Дві версії методу
String[] split(String regex) // limit = 0
String[] split(String regex, int limit) // повний контроль
Параметр limit:
| Limit | Поведіння | Приклад "a,b,c,,".split(",", limit) |
| ————- | —————————————————— | ————————————- |
| 0 (default) | Максимальне розділення, порожні в кінці видаляються | ["a", "b", "c"] |
| > 0 | Не більше limit елементів, остача — в останній | ["a", "b,c,,"] (limit=2) |
| < 0 | Максимальне розділення, порожні зберігаються | ["a", "b", "c", "", ""] |
Швидкий шлях (Fast Path) оптимізація
split() НЕ завжди використовує важкий regex-рушій. Якщо роздільник — один символ (не regex-метасимвол), використовується прямий пошук:
// Fast Path — немає компіляції regex
"hello world".split(" ");
// Regex engine — компіляція Pattern/Matcher
"hello world".split("\\s+");
| Метасимволи, які ламають Fast Path: ., $, | , (, ), [, ], ^, ?, *, +, \ |
Типові помилки
-
Помилка:
split(".")— крапка = “будь-який символ” в regex Рішення:split("\\.")абоsplit(Pattern.quote(".")) -
Помилка: Очікування порожніх рядків в кінці за замовчуванням Рішення: Використовуйте
split(",", -1)для збереження порожніх -
Помилка:
split()в циклі для одного й того ж regex Рішення: СкомпілюйтеPatternодин раз:Pattern.compile(",").split(str)
🔴 Senior Level
Internal Implementation
OpenJDK — String.split():
public String[] split(String regex, int limit) {
char ch = 0;
// Fast Path: один символ, не regex meta
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE)) {
// Це перевіряє, що ch НЕ є рядковою літерою (аналогічно для верхнього
// регістру та цифр) — гарантуючи, що \ не є відомим shorthand (\d, \w тощо)
// FAST PATH — прямий пошук по масиву байт
int off = 0;
int next = indexOf(ch, off);
// ... manual splitting без Pattern/Matcher
}
// SLOW PATH — через Pattern
return Pattern.compile(regex).split(this, limit);
}
Архітектурні Trade-offs
Fast Path:
- Плюси: Немає алокації Pattern/Matcher, ~5-10ns на виклик
- Мінуси: Працює тільки для найпростіших роздільників
Regex Engine (Pattern/Matcher):
- Плюси: Повна потужність регулярних виразів
- Мінуси: Компіляція regex (~1-5μs), алокації Pattern + Matcher + results
Edge Cases
- Empty input:
"".split(","); // [""] — масив з одним порожнім рядком ",".split(","); // [] — порожній масив (limit=0 видаляє порожні) ",".split(",", -1); // ["", ""] — два порожні рядки - Regex з lookahead/lookbehind:
"a1b2c3".split("(?=\\d)"); // ["a", "1b", "2c", "3"] — split перед цифрами - Trailing empty strings:
"a,,b".split(","); // ["a", "", "b"] "a,,b,,,".split(","); // ["a", "", "b"] — trailing прибрані "a,,b,,,".split(",", -1); // ["a", "", "b", "", "", ""]
Продуктивність
| Сценарій | Fast Path | Regex Engine | Pre-compiled Pattern | |
| ———————— | ———– | ————- | ——————– | —– |
| split(",") 1M раз | ~50ms | ~500ms | ~80ms | |
| split("\\ | ") 1M раз | ~60ms | ~500ms | ~80ms |
| split("\\s+") 1M раз | N/A | ~800ms | ~120ms | |
| Regex compile overhead | 0 | ~1-5μs | 0 (once) | |
Production Experience
Сценарій: Парсинг CSV (10M рядків):
// ПОГАНО — компіляція regex на кожному рядку
for (String line : lines) {
String[] fields = line.split(","); // 10M компіляцій regex!
}
// ГАРАЗД — pre-compiled Pattern
private static final Pattern COMMA = Pattern.compile(",");
for (String line : lines) {
String[] fields = COMMA.split(line);
}
// КРАЩЕ — Fast Path (один символ, не meta)
for (String line : lines) {
String[] fields = line.split(","); // Fast Path спрацює!
}
Сценарій 2: Парсинг log-файлу з regex-роздільником:
line.split("\\s\\|\\s")— не Fast Path, кожен виклик компілює regex- Fix:
private static final Pattern SEP = Pattern.compile("\\s\\|\\s"); - Результат: -80% CPU на парсинг
Monitoring
// JMH бенчмарк
@Benchmark
public String[] testSplit() {
return input.split(",");
}
@Benchmark
public String[] testPrecompiled() {
return COMMA.split(input);
}
// Profile regex compilation
java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions ...
Best Practices для Highload
- Для односимвольних роздільників (не meta):
split(",")— Fast Path - Для regex-роздільників: pre-compiled
Pattern.compile(regex).split(str) - В hot paths: розгляньте ручну реалізацію через
indexOf()— мінімум алокацій - Для CSV/TSV: спеціалізовані бібліотеки (OpenCSV, Apache Commons CSV)
- Для ultra-low-latency: zero-copy парсинг через
CharSequencewrappers
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
split(regex)розбиває рядок за регулярним виразом, повертає масив- Fast Path: для односимвольних не-meta роздільників — прямий пошук без regex engine
limitпараметр:0— порожні в кінці видаляються,< 0— зберігаються,> 0— максимум елементів- Метасимволи regex потрібно екранувати:
. $ | ( ) [ ] { } ^ ? * + \ Pattern.quote(".")— безпечний спосіб екранування дляsplit()- Компіляція regex на кожній ітерації циклу — антипатерн, використовуйте pre-compiled
Pattern
Часті уточнюючі запитання:
- Чому
split(".")не працює? — Крапка в regex = “будь-який символ”. Потрібноsplit("\\."). - Що робить
split(",", -1)? — Зберігає порожні рядки в кінці. За замовчуванням (limit=0) вони видаляються. - Що таке Fast Path в
split()? — Якщо роздільник — один не-meta символ, використовується прямий пошук без Pattern/Matcher. - Як оптимізувати
split()в циклі? — Pre-compiledPattern:private static final Pattern COMMA = Pattern.compile(",").
Червоні прапорці (НЕ говорити):
- ❌ “
split()приймає звичайний рядок, а не regex” — приймає regex, крапка зламає логіку - ❌ “
split()завжди зберігає порожні рядки” — за замовчуванням (limit=0) видаляє trailing empty - ❌ “Можна компілювати regex в циклі без наслідків” — 10M компіляцій = секунди CPU
- ❌ “
split()— єдиний спосіб розбити рядок” — єindexOf(),StringTokenizer, спеціалізовані парсери
Пов’язані теми:
- [[16. В чому особливість методу replace() vs replaceAll()]]
- [[8. Як компілятор Java оптимізує конкатенацію рядків]]
- [[7. Що відбувається при конкатенації рядків через оператор +]]