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

Strange Java Unicode Regular Expression StringIndexOutOfBoundsException

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

String line = "💕💕💕";
line.replaceAll("(?i)(.)\\1{2,}", "$1");

Сбой. Если я удалю переключатель (?i), он будет работать. Три символа unicode не являются случайными, они были найдены среди большого корейского текста, но я не знаю, что они действительны или нет.

Странно то, что регулярное выражение работает для всего другого текста, кроме этого. Почему я получаю сообщение об ошибке?

Это исключение, которое я получаю

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6
    at java.lang.String.charAt(String.java:658)
    at java.lang.Character.codePointAt(Character.java:4668)
    at java.util.regex.Pattern$CIBackRef.match(Pattern.java:4846)
    at java.util.regex.Pattern$Curly.match(Pattern.java:4125)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3694)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556)
    at java.util.regex.Pattern$Start.match(Pattern.java:3408)
    at java.util.regex.Matcher.search(Matcher.java:1199)
    at java.util.regex.Matcher.find(Matcher.java:592)
    at java.util.regex.Matcher.replaceAll(Matcher.java:902)
    at java.lang.String.replaceAll(String.java:2162)
    at tokenizer.Test.main(Test.java:51)
4b9b3361

Ответ 1

То, что объясняется Сантошем в этом ответе, неверно. Это можно продемонстрировать, запустив

String str = "💕💕💕";
System.out.println("code point: " + .codePointAt(0));

который выведет (по крайней мере для меня) значение 128149, что подтверждается этой страницей как правильно. Поэтому Java не интерпретирует строку неправильно. Он неправильно интерпретировал это при использовании метода getBytes().

Однако, как объясняет OP, кажется, что на него падает аварийное выражение. У меня нет другого объяснения, поскольку это ошибка в java. Либо это, либо он не поддерживает UTF-16 полностью по дизайну.

Edit:

на основе этого ответа:

компилятор регулярных выражений присоединяется к UTF-16. Опять же, это никогда не может быть исправлено или оно изменит старые программы. Вы даже не можете обойти ошибка с помощью обычного обходного пути для Javas Unicode-in-source-code проблемы путем компиляции с java-кодированием UTF-8, потому что глупый вещь хранит строки как неприятные UTF-16, которые обязательно ломаются их в классах символов. OOPS!

Казалось бы, это ограничение регулярных выражений в java.


Поскольку вы прокомментировали, что

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

Это, безусловно, можно сделать. Прямым способом является применение вашего регулярного выражения только к определенному диапазону. Фильтрация диапазонов символов Юникода объясняется в этом ответе. Основываясь на этом ответе, пример, который, похоже, не задыхается, а просто оставляет проблемные символы:

line.replaceAll("(?Ui)([\\u0000-\\uffff])\\1{2,}", "$1")    

// "💕💕💕" -> "💕💕💕"
// "foo 💕💕💕 foo" -> "foo 💕💕💕 foo"
// "foo aAa foo" -> "foo a foo"

Ответ 2

Символы, о которых вы упоминали, на самом деле " "Двухбайтовые символы" . Это означает, что два байта образуют один символ. Но для того, чтобы это интерпретировать Java, информация о кодировании (когда она отличается от кодировки платформы по умолчанию) должна передаваться явно (или будет использоваться кодировка платформы по умолчанию).

Чтобы доказать это, рассмотрим следующее

String line = "💕💕💕";
System.out.println(line.length());

это печатает длину как 6! Если у нас есть только три символа,

теперь следующий код

String line1 = new String("💕💕💕".getBytes(),"UTF-8");
System.out.println(line1.length());

печатает длину как 3, которая предназначена.

если вы замените строку

String line = "💕💕💕";

с

 String line1 = new String("💕💕💕".getBytes(),"UTF-8");

работает, и регулярное выражение не прерывается. Я использовал UTF-8 здесь. Пожалуйста, используйте соответствующую кодировку вашей предполагаемой платформы.

Библиотеки регулярных выражений Java в значительной степени зависят от последовательности символов, которая, в свою очередь, зависит от схемы кодирования. Для строк, имеющих кодировку символов, отличную от кодировки по умолчанию, символы не могут быть правильно декодированы (они отображали 6 символов вместо 3!), И поэтому regex терпит неудачу.

Ответ 3

Собственно, это просто ошибка.

Это то, что трассировки стека и открытый исходный код для.

Если CIBackRef (для обратного обращения без учета регистра) сравнивается с группой, это не приводит к неверному индексу цикла. Это показывает исправление:

        // Check each new char to make sure it matches what the group
        // referenced matched last time around
        int x = i;
        for (int index=0; index<groupSize; ) {
            int c1 = Character.codePointAt(seq, x);
            int c2 = Character.codePointAt(seq, j);
            if (c1 != c2) {
                if (doUnicodeCase) {
                    int cc1 = Character.toUpperCase(c1);
                    int cc2 = Character.toUpperCase(c2);
                    if (cc1 != cc2 &&
                        Character.toLowerCase(cc1) !=
                        Character.toLowerCase(cc2))
                        return false;
                } else {
                    if (ASCII.toLower(c1) != ASCII.toLower(c2))
                        return false;
                }
            }
            int n = Character.charCount(c1);
            x += n;
            index += n;  // was index++
            j += Character.charCount(c2);
        }

groupSize - общий charCount группы. j - это индекс для ссылочной группы.

Тест

  //9ff0 9592 9ff0 9592 9ff0 9592
  val line = "\ud83d\udc95\ud83d\udc95\ud83d\udc95"
  Console println Try(line.replaceAll("(?ui)(.)\\1{2,}", "$1"))

не работает нормально

[email protected]:~/tmp$ skalac kcharex.scala ; skala kcharex.Test
Failure(java.lang.StringIndexOutOfBoundsException: String index out of range: 6)

но успешно с исправлением

[email protected]:~/tmp$ skala -J-Xbootclasspath/p:../bootfix kcharex.Test
Success(💕)

Другая ошибка в исходном примере кода заключается в том, что встроенные флаги должны включать ?ui. В javadoc на Pattern.CASE_INSENSITIVE говорится:

По умолчанию нечувствительность к регистру предполагает, что только символы в кодировка US-ASCII сопоставляется. Поддержка Unicode без учета регистра совпадение можно включить, указав флаг UNICODE_CASE в вместе с этим флагом.

Как вы можете видеть из фрагмента кода без u, он будет терпеть неудачу, только если ASCII.toLower не сравнится с равным, что не предназначено. Я недостаточно изощрен, чтобы знать о дополнительном характере, который не прошел бы этот тест без написания кода, чтобы понять его.