为什么代理java正则表达式找到连字符 - 减号 -

时间:2015-01-07 13:50:30

标签: java regex

我试图找到为什么([\ud800-\udbff\udc00-\udfff])中使用的JAVA replaceAll(regexp,"")中的正则表达式也删除了超负字符以及代理字符。

此版本的Unicode为\u002d,因此它似乎不在任何范围内。

我可以轻松删除此行为,添加&&[^\u002d]会导致([\ud800-\udbff\udc00-\udfff&&[^\u002d]])

但是,由于我不知道为什么删除了\u002d,我认为可能会删除更多未被注意的字符。

示例:

String text = "A\u002dB";
System.out.println(text);
String regex = "([\ud800-\udbff\udc00-\udfff])";
System.out.println(text.replaceAll(regex, "X"));

打印:

A-B
AXB

2 个答案:

答案 0 :(得分:7)

概述和假设

匹配星体平面中的字符(代码点U + 10000到U + 10FFFF)是Java正则表达式中一个记录不足的特性。

这个答案主要涉及Java版本6及更高版本的Oracle实现(参考实现,也用于OpenJDK)。

如果您碰巧使用GNU Classpath或Android,请自行测试代码,因为他们使用自己的实现。

幕后

假设您在Oracle的实现上运行正则表达式,那么正则表达式

"([\ud800-\udbff\udc00-\udfff])"

编译如下:

StartS. Start unanchored match (minLength=1)
java.util.regex.Pattern$GroupHead
Pattern.union. A ∪ B:
  Pattern.union. A ∪ B:
    Pattern.rangeFor. U+D800 <= codePoint <= U+10FC00.
    BitClass. Match any of these 1 character(s):
      [U+002D]
  SingleS. Match code point: U+DFFF LOW SURROGATES DFFF
java.util.regex.Pattern$GroupTail
java.util.regex.Pattern$LastNode
Node. Accept match

字符类被解析为\ud800-\udbff\udc00-\udfff。由于\udbff\udc00形成有效的代理项对,因此它代表代码点U + 10FC00。

错误的解决方案

写作没有意义:

"[\ud800-\udbff][\udc00-\udfff]"

由于Oracle的实现与代码点匹配,并且有效的代理对将在匹配之前转换为代码点,因此上面的正则表达式无法匹配任何内容,因为它正在搜索可以形成有效对的2个连续单独代理项。 / p>

解决方案

如果你想匹配并删除星体平面上的U + FFFF以上的所有代码点(由有效的代理对形成),加上单独的代理(不能形成有效的代理对),你应该写:

input.replaceAll("[\ud800\udc00-\udbff\udfff\ud800-\udfff]", "");

此解决方案已经过测试,可在Java 6和7(Oracle实现)中使用。

上面的正则表达式编译为:

StartS. Start unanchored match (minLength=1)
Pattern.union. A ∪ B:
  Pattern.rangeFor. U+10000 <= codePoint <= U+10FFFF.
  Pattern.rangeFor. U+D800 <= codePoint <= U+DFFF.
java.util.regex.Pattern$LastNode
Node. Accept match

请注意,我使用字符串文字Unicode转义序列指定字符,而不是正则表达式语法中的转义序列。

// Only works in Java 7
input.replaceAll("[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]", "")

当使用正则表达式语法指定代理对时,Java 6无法识别代理对,因此正则表达式将\\ud800识别为一个字符,并尝试编译失败的范围\\udc00-\\udbff。我们很幸运,它为这个输入抛出了一个例外;否则,错误将无法检测到。 Java 7正确解析了这个正则表达式,并编译成与上面相同的结构。


从Java 7及更高版本开始,添加了语法\x{h..h}以支持指定BMP(基本多语言平面)之外的字符,并且建议在星体平面中指定字符。

input.replaceAll("[\\x{10000}-\\x{10ffff}\ud800-\udfff]", "");

这个正则表达式也编译成与上面相同的结构。

答案 1 :(得分:1)

如果你制作范围

[\ud800-\udfff]

[\ud800-\udbff\udbff-\udfff]

它将使连字符保持不变。 对我来说似乎是一个错误。

请注意,没有理由选择双倍范围,在您的示例中\udc00只是\udbff之后的下一个代码点,因此您可以跳过它。如果你使两个范围重叠一个或多个代码点,它会再次起作用,但你也可以将其排除(参见上面的第一个例子)。