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

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

 private static class CaseInsensitiveComparator implements Comparator, 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; } } 

Как правило, мы ожидаем, что один раз переведем случай, сравним и сделаем с ним. Однако код преобразует случай дважды, и причина указана в комментарии к другому методу 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; } 

Из Unicode Technical Standard :

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

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

Простая проверка грубой силы дает некоторые результаты. Проверьте, например, коды 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», и это дает основание рассматривать их как одни и те же значения в случае нечувствительного сравнения.

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

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

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

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

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

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

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

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

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

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

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

  • Json Преобразует пустую строку вместо null
  • Java String.split () иногда дает пустые строки
  • Можно ли использовать std :: basic_string в качестве непрерывного буфера при таргетинге на C ++ 03?
  • Ввод Java String Scanner не дожидается информации, перемещается непосредственно в следующий оператор. Как дождаться информации?
  • Токенизация строк в C
  • Использование конструктора String (String) в Java
  • Как обрезать пробелы из переменной Bash?
  • Как удалить пустые строки из форматированной строки?
  • Почему изменение строки через извлеченный указатель на его данные не разрешено?
  • Сравнение строк и преобразование строк в Java
  • Разница между строкой replace () и replaceAll ()
  • Давайте будем гением компьютера.