奇怪的Java Unicode正则表达式StringIndexOutOfBoundsException

时间:2013-04-15 06:32:30

标签: java regex unicode

我的问题很简单但令人费解。可能有一个简单的开关可以解决这个问题但我在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)

3 个答案:

答案 0 :(得分:3)

您提到的字符实际上是“Double byte characters”。这意味着两个字节组成一个字符。但是对于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正则表达式库严重依赖于Character Sequence,{{3}}又取决于编码方案。对于具有与默认编码不同的字符编码的字符串,字符无法正确解码(它显示6个字符而不是3个!)因此正则表达式失败。

答案 1 :(得分:1)

Santosh在this answer中解释的是不正确的。这可以通过运行

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

将输出(至少对我而言)值128149,由this page确认为正确。因此Java不会以错误的方式解释字符串。它在使用getBytes()方法时确实解释错误。

然而,正如OP所解释的那样,正则表达式似乎崩溃了。我没有其他解释,因为它是java中的一个错误。要么那么,或者它完全不支持UTF-16的设计。

编辑:

基于this answer

  

正则表达式编译器搞砸了UTF-16。同样,这永远不会   修复或它将改变旧程序。你甚至无法绕过   使用Java的Unicode-in-source-code的常规解决方法   通过编译java -encoding UTF-8来解决问题,因为愚蠢   thing将字符串存储为令人讨厌的UTF-16,这必然会破坏   他们在角色课上。 OOPS!

这似乎是java中正则表达式的限制。


因为您评论了

  

如果我可以简单地忽略UTF-16字符,那将是最好的   应用正则表达式而不是抛出异常。

这当然可以做到。一种简单的方法是仅将正则表达式应用于特定范围。在this answer中已经解释了过滤unicode字符范围。基于这个答案,这个例子似乎没有扼杀,只留下了问题角色:

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

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

答案 2 :(得分:1)

实际上,这只是一个错误。

这就是堆栈跟踪和开源的用途。

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"))

正常失败

apm@mara:~/tmp$ skalac kcharex.scala ; skala kcharex.Test
Failure(java.lang.StringIndexOutOfBoundsException: String index out of range: 6)

但成功修复了

apm@mara:~/tmp$ skala -J-Xbootclasspath/p:../bootfix kcharex.Test
Success()

原始示例代码中的另一个错误是内联标志应包含?uiPattern.CASE_INSENSITIVE上的javadoc说:

  

默认情况下,不区分大小写的匹配假定只包含字符   US-ASCII字符集正在匹配。 Unicode感知不区分大小写   可以通过在中指定UNICODE_CASE标志来启用匹配   与这面旗帜结合。

正如您在代码段中看到的那样,如果没有u,只有当ASCII.toLower不比较相等时才会失败,这是不可取的。我不够精明,无法知道一个补充字符,如果没有编写代码来解决这个问题就会失败。