Как работает метод 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. Что происходит при конкатенации строк через оператор +]]