Как сравнить строки без учёта регистра



В этом посте расскажу, как решить задачу из предыдущего поста и сделать метод быстрее, чем в JDK.



Напомню задачу: сравнить две строки без учёта регистра и оценить производительность в разных ситуациях.



Шаг 1. Смотрим подходящие методы в классе String



s1.toLowerCase().equals(s2.toLowerCase())

s1.equalsIgnoreCase(s2)

s1.compareToIgnoreCase(s2) != 0

s1.regionMatches(true, 0, s2, 0, s2.length())



Шаг 2. Предположим возможные ситуации



Строки могут сильно и слабо отличаться по регистру.



Скорее всего, алгоритмы для пустых, коротких и длинных строк одни и те же, так что не будем различать эти случаи.



Шаг 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;



Лучшее из двух миров — быстрая проверка по длине и нет лишних апперкейсов.



Ниже — бенчмарки всех вариантов. Результаты на разных железках могут отличаться!