我的问题很简单但令人费解。可能有一个简单的开关可以解决这个问题但我在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)
答案 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()
原始示例代码中的另一个错误是内联标志应包含?ui
。 Pattern.CASE_INSENSITIVE
上的javadoc说:
默认情况下,不区分大小写的匹配假定只包含字符 US-ASCII字符集正在匹配。 Unicode感知不区分大小写 可以通过在中指定UNICODE_CASE标志来启用匹配 与这面旗帜结合。
正如您在代码段中看到的那样,如果没有u
,只有当ASCII.toLower不比较相等时才会失败,这是不可取的。我不够精明,无法知道一个补充字符,如果没有编写代码来解决这个问题就会失败。