Подтвердить что ты не робот

Почему String.equalsIgnoreCase настолько медленный

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

Я ответил на это, используя разницу значений ASCII для каждой пары символов. Но дома, когда я прошел через фактическую реализацию его в String.class, меня беспокоит - почему это реализовано именно так?

Я попытался провести сравнение между встроенным и моим пользовательским методом, таким образом -

public class EqualsIgnoreCase {

    public static void main(String[] args) {
        String str1 = "Srimant @$ Sahu 959s";
        String str2 = "sriMaNt @$ sAhu 959s";

        System.out.println("Avg millisecs with inbuilt () - " + averageOfTenForInbuilt(str1, str2));
        System.out.println("\nAvg millisecs with custom () - " + averageOfTenForCustom(str1, str2));
    }

    public static int averageOfTenForInbuilt(String str1, String str2) {
        int avg = 0;
        for (int itr = 0; itr < 10; itr++) {
            long start1 = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                str1.equalsIgnoreCase(str2);
            }
            avg += System.currentTimeMillis() - start1;
        }
        return avg / 10;
    }

    public static int averageOfTenForCustom(String str1, String str2) {
        int avg = 0;
        for (int itr = 0; itr < 10; itr++) {
            long start2 = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                isEqualsIgnoreCase(str1, str2);
            }
            avg += System.currentTimeMillis() - start2;
        }
        return avg / 10;
    }

    public static boolean isEqualsIgnoreCase(String str1, String str2) {
        int length = str1.length();
        if (str2.length() != length) {
            return false;
        }

        for (int i = 0; i < length; i++) {
            char ch1 = str1.charAt(i);
            char ch2 = str2.charAt(i);

            int val = Math.abs(ch1 - ch2);
            if (val != 0) {
                if (isInAlphabetsRange(ch1, ch2)) {
                    if (val != 32) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
        return true;
    }

    public static boolean isInAlphabetsRange(char ch1, char ch2) {
        return (((ch1 <= 122 && ch1 >= 97) || (ch1 <= 90 && ch1 >= 65)) && ((ch2 <= 122 && ch2 >= 97) || (ch2 <= 90 && ch2 >= 65)));
    }

}

Вывод -

Средние миллисекунды со встроенными() - 14

Средние миллисекунды с пользовательскими() - 5

Я обнаружил, что встроенный метод эффективен, поскольку из-за большого количества проверок и вызовов методов. Существуют ли какие-либо конкретные причины такой реализации? Или я что-то пропущу в своей логике?

Любые предложения, будут сердечно оценены!

4b9b3361

Ответ 1

Ваша рутина обрабатывает только символы ASCII. Система обрабатывает все символы Юникода.

Рассмотрим следующий пример:

public class Test {

    public static void main(String[] args) {
        System.out.println((int) 'ě'); // => 283
        System.out.println((int) 'Ě'); // => 282 
    }

}

Ответ 2

Ваш метод неверен по-разному. Например, он считает "!" равным "B", "B", равным "1", но "!" не равным "1" (поэтому он не является транзитивным, как можно было бы ожидать, что метод равен).

Да, довольно легко написать неверную реализацию для этого метода, которая является более быстрой и простой. Сложной задачей было бы написать правильный, т.е. Правильно обрабатывать все аргументы, выполняемые реализацией JDK.

Вы также можете посмотреть Как написать правильный микро-тест в Java?, чтобы получить более надежные измерения производительности.

Ответ 3

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

Есть некоторые (раздражающие) локали, для которых два символа могут иметь один и тот же верхний регистр, но не тот же самый нижний регистр. По этой причине, чтобы работать (большую часть времени см. на турецком языке), каноническая реализация должна сравнивать строки char -for- char как в их нижнем, так и в верхнем случаях.

Ваша реализация, вероятно, идеальна в 99% случаев, особенно если вам нужно иметь дело только с английским языком, но реализация основной библиотеки, к сожалению, не может сделать такие предположения.

Ответ 4

Я думаю, что проверка

String1.equalsIgnoreCase(String2)

тот, который предоставлен, имеет гораздо лучшее признание персонажа и принимает все Значения символов, включенные в Unicode, но; то, что вы пытались выяснить через свой собственный код, заключается в том, что вы сравниваете только английские алфавитные символы.

Итак, я думаю, в строках Pavel Horel, комментаторе вашего сообщения, что из-за сложности, которую он обеспечивает для сравнения между всеми видами символов Unicode, может потребоваться больше времени.

Ответ 5

Я думаю, что это exerpt из String.java имеет значение:

if (ignoreCase) {
    // If characters don't match but case may be ignored,
    // try converting both characters to uppercase.
    // If the results match, then the comparison scan should
    // continue.
    char u1 = Character.toUpperCase(c1);
    char u2 = Character.toUpperCase(c2);
    if (u1 == u2) {
        continue;
    }
    // Unfortunately, conversion to uppercase does not work properly
    // for the Georgian alphabet, which has strange rules about case
    // conversion.  So we need to make one last check before
    // exiting.
    if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
        continue;
    }
}