14

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

3 回答 3

3

您提到的字符实际上是“双字节字符”。这意味着两个字节形成一个字符。但是对于 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 个!),因此正则表达式失败。

于 2013-04-15T09:53:15.950 回答
1

Santosh 在此答案中的解释是不正确的。这可以通过运行来证明

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

这将输出(至少对我而言)值 128149,此页面已确认该值是正确的。所以Java不会以错误的方式解释字符串。使用 getBytes() 方法时,它确实解释错了。

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

编辑:

基于这个答案

正则表达式编译器搞砸了 UTF-16。同样,这永远无法修复,否则它将改变旧程序。您甚至无法通过使用 java -encoding UTF-8 编译来解决 Java 的 Unicode-in-source-code 问题的正常解决方法来解决该错误,因为愚蠢的东西将字符串存储为讨厌的 UTF-16,这必然会破坏它们字符类。哎呀!

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


既然你这么评论

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

这当然可以做到。一种直接的方法是将您的正则表达式仅应用于某个范围。此答案中已解释了过滤 unicode 字符范围。基于该答案,该示例似乎并没有令人窒息,而只是将问题字符留在了原处:

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

// "" -> ""
// "foo  foo" -> "foo  foo"
// "foo aAa foo" -> "foo a foo"
于 2013-04-15T19:16:25.570 回答
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()

原始示例代码中的另一个错误是内联标志应该包括?ui. 上的 javadocPattern.CASE_INSENSITIVE说:

默认情况下,不区分大小写的匹配假定仅匹配 US-ASCII 字符集中的字符。可以通过将 UNICODE_CASE 标志与此标志一起指定来启用 Unicode 感知的不区分大小写匹配。

正如您从代码片段中看到的那样,u如果没有 ASCII.toLower 不比较相等,它只会失败,这不是故意的。我不够老练,不知道有一个补充字符会在不编写代码来解决的情况下通过该测试。

于 2013-08-17T19:19:40.620 回答