Как сравнить строки без учёта регистра
В этом посте расскажу, как решить задачу из предыдущего поста и сделать метод быстрее, чем в JDK.
Напомню задачу: сравнить две строки без учёта регистра и оценить производительность в разных ситуациях.
Шаг 1. Смотрим подходящие методы в классе String
Строки могут сильно и слабо отличаться по регистру.
Скорее всего, алгоритмы для пустых, коротких и длинных строк одни и те же, так что не будем различать эти случаи.
Шаг 3. Углубляемся в реализацию
🔸 toLowerCase + equals
▫️ Для каждой строки создаёт копию в нижнем регистре. Затем включается обычный equals:
▫️ Сравнивает длины строк. Если не равны, сразу возвращается false
▫️ Сравнивает по одному символу, пока не дойдёт до конца строки или пока символы не будут разными
✖️В начале целиком обходим s1 и s2, чтобы создать копии в нижнем регистре. Когда длины строк отличаются (что можно узнать сразу), эта работа бесполезна
✖️ Если строки мало отличаются по регистру, то приведение всех символов к одному регистру будет лишним
🔸 compareToIgnoreCase cравнивает элементы по порядку. Если символы не равны — вызывает апперкейс и сравнивает ещё раз.
✔️ Uppercase происходит только при необходимости. Если разница в регистрах небольшая (s1=java, s2=Java), то этот подход будет быстрее
✖️ Цель метода — сравнить строки, поэтому нет быстрой проверки длины
🔸 regionMatches берёт символы из строк s1 и s2, сразу делает uppercase и сравнивает
✔️ Если строки по регистрам сильно отличаются, предварительный апперкейс ускорит проверку
✖️ Метод работает с подстроками произвольной длины, поэтому нет быстрой проверки длин
🔸 equalsIgnoreCase сравнивает длины строк, потом вызывает regionMatches
Итог
▪️ Если строки разной длины, то однозначно побеждает equalsIgnoreCase
▪️ Если длины одинаковы и
— регистры не сильно отличаются, то побеждает compareToIgnoreCase
— нужно много преобразований — regionMatches
equalsIgnoreCase для каждого символа делается апперкейс. Если строки по регистрам слабо отличаются, то это явно лишнее. Для таких случаев подойдёт кастомный метод:
Ниже — бенчмарки всех вариантов. Результаты на разных железках могут отличаться!
В этом посте расскажу, как решить задачу из предыдущего поста и сделать метод быстрее, чем в JDK.
Напомню задачу: сравнить две строки без учёта регистра и оценить производительность в разных ситуациях.
Шаг 1. Смотрим подходящие методы в классе String
s1.toLowerCase().equals(s2.toLowerCase())Шаг 2. Предположим возможные ситуации
s1.equalsIgnoreCase(s2)
s1.compareToIgnoreCase(s2) != 0
s1.regionMatches(true, 0, s2, 0, s2.length())
Строки могут сильно и слабо отличаться по регистру.
Скорее всего, алгоритмы для пустых, коротких и длинных строк одни и те же, так что не будем различать эти случаи.
Шаг 3. Углубляемся в реализацию
🔸 toLowerCase + equals
▫️ Для каждой строки создаёт копию в нижнем регистре. Затем включается обычный equals:
▫️ Сравнивает длины строк. Если не равны, сразу возвращается false
▫️ Сравнивает по одному символу, пока не дойдёт до конца строки или пока символы не будут разными
✖️В начале целиком обходим s1 и s2, чтобы создать копии в нижнем регистре. Когда длины строк отличаются (что можно узнать сразу), эта работа бесполезна
✖️ Если строки мало отличаются по регистру, то приведение всех символов к одному регистру будет лишним
🔸 compareToIgnoreCase cравнивает элементы по порядку. Если символы не равны — вызывает апперкейс и сравнивает ещё раз.
✔️ Uppercase происходит только при необходимости. Если разница в регистрах небольшая (s1=java, s2=Java), то этот подход будет быстрее
✖️ Цель метода — сравнить строки, поэтому нет быстрой проверки длины
🔸 regionMatches берёт символы из строк s1 и s2, сразу делает uppercase и сравнивает
✔️ Если строки по регистрам сильно отличаются, предварительный апперкейс ускорит проверку
✖️ Метод работает с подстроками произвольной длины, поэтому нет быстрой проверки длин
🔸 equalsIgnoreCase сравнивает длины строк, потом вызывает regionMatches
Итог
▪️ Если строки разной длины, то однозначно побеждает equalsIgnoreCase
▪️ Если длины одинаковы и
— регистры не сильно отличаются, то побеждает compareToIgnoreCase
— нужно много преобразований — regionMatches
equalsIgnoreCase для каждого символа делается апперкейс. Если строки по регистрам слабо отличаются, то это явно лишнее. Для таких случаев подойдёт кастомный метод:
if (s1.length() != s2.length()) {Лучшее из двух миров — быстрая проверка по длине и нет лишних апперкейсов.
return false;
}
return s1.compareToIgnoreCase(s2) != 0;
Ниже — бенчмарки всех вариантов. Результаты на разных железках могут отличаться!