为什么代理java regexp发现hypen-minus –
我试图找到为什么在replaceAll(regexp,"")
使用的JAVA ([\ud800-\udbff\udc00-\udfff])
中的正则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"));
打印:
AB AXB
概述和假设
星象平面中的匹配字符(代码点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个连续单独代理。
解
如果你想匹配并删除星体平面上的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]", "");
这个正则表达式也编译成与上面相同的结构。
如果你做范围
[\ud800-\udfff]
要么
[\ud800-\udbff\udbff-\udfff]
它会使连字符不受影响。 对我来说似乎是个错误。
请注意,没有理由使用双范围,在您的示例中\udc00
只是\udbff
之后的下一个代码点,因此您可以跳过它。 如果你使两个范围重叠一个或多个代码点,它会再次起作用,但你也可以将其排除(参见上面的第一个例子)。