为什么java中的String.replaceAll()需要在正则表达式中使用4个斜杠“\\\\”来实际替换“\”?
我最近注意到,当涉及转义字符“\”(斜杠)时,String.replaceAll(正则表达式,替换)表现得非常奇怪。
例如,考虑有一个带有filepath的字符串 – String text = "E:\\dummypath"
,我们想用"/"
替换"\\"
"/"
。
text.replace("\\","/")
给出输出"E:/dummypath"
text.replaceAll("\\","/")
而text.replaceAll("\\","/")
引发exceptionjava.util.regex.PatternSyntaxException
。
如果我们想用replaceAll()
实现相同的function,我们需要将其写为text.replaceAll("\\\\","/")
一个值得注意的区别是replaceAll()
将其参数作为reg-ex,而replace()
具有参数character-sequence!
但text.replaceAll("\n","/")
与其char序列等效text.replace("\n","/")
完全相同
深入挖掘:当我们尝试其他一些输入时,可以观察到更奇怪的行为。
让我们分配text="Hello\nWorld\n"
现在, text.replaceAll("\n","/")
, text.replaceAll("\\n","/")
, text.replaceAll("\\\n","/")
所有这三个给出相同的输出Hello/World/
Java以我认为最好的方式真的搞砸了reg-ex! 没有其他语言似乎在reg-ex中有这些有趣的行为。 有什么特别的原因,为什么Java搞砸了这样?
@Peter Lawrey的回答描述了这些机制。 “问题”是反斜杠是Java字符串文字和正则表达式的迷你语言中的转义字符。 因此,当您使用字符串文字来表示正则表达式时,需要考虑两组转义…取决于您希望正则表达式的含义。
但为什么会那样?
这是历史性的事情。 Java最初根本没有正则表达式。 Java String文字的语法规则借用了C / C ++,它也没有内置的正则表达式支持。 在Java 1.4中以Pattern
类的forms添加正则表达式支持之前,双重转义的重要性在Java中并不明显。
那么其他语言如何设法避免这种情况呢?
他们通过在编程语言本身中为正则表达式提供直接或间接的语法支持来实现它 。 例如,在Perl,Ruby,Javascript和许多其他语言中,有一种模式/正则表达式的语法(例如’/ pattern /’),其中字符串文字转义规则不适用。 在C#和Python中,它们提供了另一种“原始”字符串文字语法,其中反斜杠不会转义。 (但请注意,如果使用普通的C#/ Python字符串语法,则会出现双重转义的Java问题。)
为什么
text.replaceAll("\n","/")
,text.replaceAll("\\n","/")
和text.replaceAll("\\\n","/")
都给出了相同的输出?
第一种情况是字符串级别的换行符。 Java正则表达式语言将所有非特殊字符视为自己匹配。
第二种情况是反斜杠,后跟字符串级别的“n”。 Java正则表达式语言解释反斜杠后跟“n”作为换行符。
最后一种情况是反斜杠,后跟字符串级别的换行符。 Java正则表达式语言不会将其识别为特定(正则表达式)转义序列。 但是在正则表达式语言中,反斜杠后跟任何非字母字符表示后一个字符。 因此,反斜杠后跟换行符……意味着与换行符相同。
你需要esacpe两次,一次用于Java,一次用于正则表达式。
Java代码是
"\\\\"
制作一个正则表达式的字符串
"\\" - two chars
但是正则表达式也需要逃避,所以它变成了
\ - one symbol
1)假设您要使用Java的replaceAll
方法替换单个\
:
\ ˪--- 1) the final backslash
2)Java的replaceAll
方法将正则表达式作为第一个参数。 在正则表达式文字中 , \
具有特殊含义,例如在\d
,它是[0-9]
(任何数字)的快捷方式。 在正则表达式文字中逃脱元数据的方法是在它之前加上\
,这导致:
\\ |˪--- 1) the final backslash ˪---- 2) the backslash needed to escape 1) in a regex literal
3)在Java中,没有正则表达式文字 :你在字符串文字中写一个正则表达式(例如,与JavaScript不同,你可以写/\d+/
)。 但是在字符串文字中 , \
也有特殊含义,例如\n
(新行)或\t
(制表符)。 在字符串文字中转义元数据的方法是在它之前加上\
,这会导致:
\\\\
˪— 1) the final backslash ||˪—- 3) the backslash needed to escape 1) in a string literal |˪—– 2) the backslash needed to escape 1) in a regex literal ˪—— 3) the backslash needed to escape 2) in a string literal
这是因为Java试图在替换字符串中赋予\
一个特殊含义,因此\ $将是一个文字$符号,但在这个过程中它们似乎已经删除了实际的特殊含义\
虽然text.replaceAll("\\\\","/")
,至少可以被认为在某种意义上是可以的(虽然它本身并不是绝对正确的),所有三个执行, text.replaceAll("\n","/")
, text.replaceAll("\\n","/")
, text.replaceAll("\\\n","/")
给出相同的输出似乎更有趣。 由于同样的原因,为什么他们限制了text.replaceAll("\\","/")
的function,这恰恰是矛盾的。
Java并没有搞乱正则表达式。 这是因为,Java完全不需要时,试图通过尝试做一些独特而不同的事情来搞乱程序员。
解决此问题的一种方法是用另一个字符替换反斜杠,使用该替代字符进行中间替换,然后在最后将其转换回反斜杠。 例如,要将“\ r \ n”转换为“\ n”:
String out = in.replace('\\','@').replaceAll("@r@n","@n").replace('@','\\');
当然,如果您选择输入字符串中可能出现的替换字符,那将无法正常工作。
我认为java真的搞乱了String.replaceAll()中的正则表达式;
除了java,我从来没有见过这种语言解析正则表达式。 如果你在其他一些语言中使用正则表达式,你会感到困惑。
如果在替换字符串中使用"\\"
,则可以使用java.util.regex.Matcher.quoteReplacement(String)
String.replaceAll("/", Matcher.quoteReplacement("\\"));
通过使用此Matcher
类,您可以获得预期的结果。