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

Понимание логики в CaseInsensitiveComparator

Может ли кто-нибудь объяснить следующий код из String.java, в частности, почему существуют три оператора if (которые я отметил //1, //2 и //3)?

private static class CaseInsensitiveComparator
                     implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;

    public int compare(String s1, String s2) {
        int n1=s1.length(), n2=s2.length();
        for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) {
            char c1 = s1.charAt(i1);
            char c2 = s2.charAt(i2);
            if (c1 != c2) {/////////////////////////1
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {/////////////////////////2
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {/////////////////////////3
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }
}
4b9b3361

Ответ 1

Как правило, мы ожидаем, что один раз переведем случай, сравним и сделаем с ним. Однако код преобразует случай дважды, и причина указана в комментарии по другому методу public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len):

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


Приложение

Код regionMatches имеет несколько отличий от кода в CaseInsenstiveComparator, но по существу делает то же самое. Полный код метода приведен ниже для целей перекрестной проверки:

public boolean regionMatches(boolean ignoreCase, int toffset,
                       String other, int ooffset, int len) {
    char ta[] = value;
    int to = offset + toffset;
    char pa[] = other.value;
    int po = other.offset + ooffset;
    // Note: toffset, ooffset, or len might be near -1>>>1.
    if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) ||
            (ooffset > (long)other.count - len)) {
        return false;
    }
    while (len-- > 0) {
        char c1 = ta[to++];
        char c2 = pa[po++];
        if (c1 == c2) {
            continue;
        }
        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;
            }
        }
        return false;
    }
    return true;
}

Ответ 2

Из Технический стандарт Unicode:

Кроме того, из-за капризов естественного языка существуют ситуации, когда два разных символа Юникода имеют одинаковые прописные или строчные буквы

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

Простая проверка грубой силы дает некоторые результаты. Проверьте, например, коды 73 и 304:

char ch1 = (char) 73; //LATIN CAPITAL LETTER I
char ch2 = (char) 304; //LATIN CAPITAL LETTER I WITH DOT ABOVE
System.out.println(ch1==ch2);
System.out.println(Character.toUpperCase(ch1)==Character.toUpperCase(ch2));
System.out.println(Character.toLowerCase(ch1)==Character.toLowerCase(ch2));

Вывод:

false
false
true

Итак, "İ" и "I" не равны друг другу. Оба символа имеют верхний регистр. Но они имеют одно и то же строчное письмо: "i", и это дает основание рассматривать их как одинаковые значения в случае нечувствительного сравнения.

Ответ 3

В другом ответе локаль по умолчанию уже привела пример того, почему для сравнения недостаточно только прописных букв, а именно буквы ASCII "I" и капитала я с точкой "İ" .

Теперь вы можете задаться вопросом: почему они не просто сравнивают только строчные буквы, а не как верхний, так и нижний регистр, если он ловит больше случаев, чем в верхнем регистре? Ответ: он не улавливает больше случаев, он просто находит разные случаи.

Возьмите букву "ı" ((char)305, маленькая точка) и ascii "i". Они разные, их строчные буквы разные, но они имеют одну и ту же прописную букву "I".

И, наконец, сравните капитал я с точкой "İ" с небольшим бесцеремонным я "ı" . Ни их верхние регионы ( "İ" , "I" ), ни их нижние регионы ( "i" и "ı" ) не совпадают, но строчные буквы их прописных букв одинаковы ( "I" ). Я нашел другой случай, если это явление, в греческих буквах "Θ" и "θ" (char 1012 и 977).

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

Ответ 4

Рассмотрим следующие символы: f и f. Начальный оператор if возвращает false, потому что они не совпадают. Однако, если вы используете оба символа, вы получаете f и f. Тогда они будут соответствовать. То же самое было бы неверно, скажем, c и G.

Код эффективен. Нет необходимости использовать оба символа, если они уже совпадают (следовательно, первый оператор if). Однако, если они не совпадают, нам нужно проверить, отличаются ли они только в случае (следовательно, второй оператор if).

Заключительный оператор if используется для определенных алфавитов (таких как грузинский), где капитализация является сложным делом. Честно говоря, я не очень много знаю о том, как это работает (просто верьте, что Oracle делает!).

Ответ 5

В приведенном выше случае для случая нечувствительного сравнения предполагаем s1 = "Apple" и s2 = "apple" В этом случае 'A'!= 'A', поскольку значения ascii обоих символов различаются, тогда он меняет оба символа на верхний регистр и снова сравнивает, тогда цикл продолжает получать окончательное значение n1-n2 = 0, таким образом, строки становятся одинаковыми. Предположим, если символы не равны при второй проверке

if (c1 != c2) {
return c1 - c2;
}

возвращает разницу в значении ascii двух символов.