Разделение строк и велосипеды
Простой способ разделить строку на части — встроенный метод
Исходный код split выглядит как-то так:
Разделитель — один символ
Сразу видим fast path для разделителей из одного символа. Алгоритм в этом случае не использует регулярку и выполняется быстрее. Но проверка, что строка-разделитель является символом, занимает 10(!) строк.
🚲 Напишем свой split — изменим тип входных данных на char, чтобы проверку делал компилятор:
Разделитель — несколько символов
Также в глаза бросается работа с регуляркой, если разделитель состоит из нескольких символов. Компиляция регулярного выражения выполняется долго, для набора строк кажется разумным выполнить её один раз:
Насколько это поможет — проверим в бенчмарке.
С другой стороны, кажется, что регулярка — это слишком серьёзное решение. Если разделитель — двоеточие с пробелом, то мощь регулярного выражения здесь не нужна.
🚲 Поэтому напишем второй велосипед, который очень похож на первый. Будем искать в исходной строке подстроку-разделитель. По полученным индексам делить строку на части.
Оба велосипеда припаркованы тут: 🚲🚲
Оценим готовые решения
Библиотека Apache Commons предлагает метод split, но работает он чуть по-другому. Метод ищет только первый разделитель и делит строку максимум на две части:
Результаты
на картинке внизу. Результаты на разных железках могут отличаться.
🔸 Оба велосипеда в пух и прах разбили стандартный split.
К слову, это не первый велосипед, который выигрывает у JDK. Такое уже случалось при сравнение строк. Надо бы завести тикет по этим кейсам:)
🔸 Если строка делится только на две части — подойдёт split из Apache Commons
❗️Для нормальной нагрузки и однократного вызова подойдёт стандартный
Не могу не отметить, что хотя подобные задачи появляются редко, они приносят море удовольствия. Разобрать код в деталях, найти пути улучшения, и в итоге метод выполняется в 2 раза быстрее, красота😊
Простой способ разделить строку на части — встроенный метод
split
:"123-456-789".split("-") → [123, 456, 789]Сегодня разберём, насколько оптимально работает метод, и как написать код быстрее, чем JDK.
Исходный код split выглядит как-то так:
public String[] split(String regex) {Разберём обе ветки этого кода
if (разделитель — один символ)
пройтись по всем символам. Встречаем разделитель - извлекаем подстроку
} else {
Pattern.compile(regex).split(this)
}
Разделитель — один символ
Сразу видим fast path для разделителей из одного символа. Алгоритм в этом случае не использует регулярку и выполняется быстрее. Но проверка, что строка-разделитель является символом, занимает 10(!) строк.
🚲 Напишем свой split — изменим тип входных данных на char, чтобы проверку делал компилятор:
split(String regex) → split(char delim)Остальной код остаётся тем же. Мы только убрали лишние условия. Потом проверим, стало ли лучше:)
Разделитель — несколько символов
Также в глаза бросается работа с регуляркой, если разделитель состоит из нескольких символов. Компиляция регулярного выражения выполняется долго, для набора строк кажется разумным выполнить её один раз:
List<String> list = ……
❌ list.stream().map(s -> s.split(": "))…
✅ Pattern p = Pattern.compile(": ");
list.stream().map(s -> splitPattern.split(s))
Насколько это поможет — проверим в бенчмарке.
С другой стороны, кажется, что регулярка — это слишком серьёзное решение. Если разделитель — двоеточие с пробелом, то мощь регулярного выражения здесь не нужна.
🚲 Поэтому напишем второй велосипед, который очень похож на первый. Будем искать в исходной строке подстроку-разделитель. По полученным индексам делить строку на части.
Оба велосипеда припаркованы тут: 🚲🚲
Оценим готовые решения
Библиотека Apache Commons предлагает метод split, но работает он чуть по-другому. Метод ищет только первый разделитель и делит строку максимум на две части:
StringUtils.split("1-2-3", "-");Ставим минус за неспортивное поведение, но включаем метод в наши эксперименты.
// получим 2 строки: "1" и "2-3"
Результаты
на картинке внизу. Результаты на разных железках могут отличаться.
🔸 Оба велосипеда в пух и прах разбили стандартный split.
String#split
слишком универсальный, и как любое универсальное решение, проигрывает кастомизированному. Если деление строк — ваш hot spot, не стесняйтесь написать свой метод. К слову, это не первый велосипед, который выигрывает у JDK. Такое уже случалось при сравнение строк. Надо бы завести тикет по этим кейсам:)
🔸 Если строка делится только на две части — подойдёт split из Apache Commons
❗️Для нормальной нагрузки и однократного вызова подойдёт стандартный
String#split
. Оптимизации нужны, когда деление строк происходит очень часто. Не могу не отметить, что хотя подобные задачи появляются редко, они приносят море удовольствия. Разобрать код в деталях, найти пути улучшения, и в итоге метод выполняется в 2 раза быстрее, красота😊