[英]Why String.replaceAll() in java requires 4 slashes “\\\\” in regex to actually replace “\”?
我最近注意到,當涉及轉義字符“\\”(斜杠)時,String.replaceAll(正則表達式,替換)表現得非常奇怪。
例如,考慮有一個帶有文件路徑的字符串 - String text = "E:\\\\dummypath"
,我們想用"/"
替換"\\\\"
"/"
。
text.replace("\\\\","/")
給出輸出"E:/dummypath"
text.replaceAll("\\\\","/")
而text.replaceAll("\\\\","/")
引發異常java.util.regex.PatternSyntaxException
。
如果我們想用replaceAll()
實現相同的功能,我們需要將其寫為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搞砸了這樣?
你需要esacpe兩次,一次用於Java,一次用於正則表達式。
Java代碼是
"\\\\"
制作一個正則表達式的字符串
"\\" - two chars
但正則表達式也需要逃避,所以它變成了
\ - one symbol
@Peter Lawrey的回答描述了這些機制。 “問題”是反斜杠是Java字符串文字和正則表達式的迷你語言中的轉義字符。 因此,當您使用字符串文字來表示正則表達式時,有兩組轉義需要考慮...取決於您希望正則表達式的含義。
但為什么會那樣?
這是歷史性的事情。 Java最初根本沒有正則表達式。 Java字符串文字的語法規則是從C / C ++中借用的,它也沒有內置的正則表達式支持。 在Java 1.4中以Pattern
類的形式添加正則表達式支持之前,雙重轉義的重要性在Java中並不明顯。
那么其他語言如何設法避免這種情況呢?
他們通過在編程語言本身中為正則表達式提供直接或間接的語法支持來實現它 。 例如,在Perl,Ruby,Javascript和許多其他語言中,有一種模式/正則表達式的語法(例如'/ pattern /'),其中字符串文字轉義規則不適用。 在C#和Python中,它們提供了另一種“原始”字符串文字語法,其中反斜杠不會轉義。 (但請注意,如果使用普通的C#/ Python字符串語法,則存在雙重轉義的Java問題。)
為什么
text.replaceAll("\\n","/")
,text.replaceAll("\\\\n","/")
和text.replaceAll("\\\\\\n","/")
都給出了相同的輸出?
第一種情況是字符串級別的換行符。 Java正則表達式語言將所有非特殊字符視為自己匹配。
第二種情況是反斜杠,后跟字符串級別的“n”。 Java正則表達式語言解釋反斜杠后跟“n”作為換行符。
最后一種情況是反斜杠,后跟字符串級別的換行符。 Java正則表達式語言不會將其識別為特定(正則表達式)轉義序列。 但是在正則表達式語言中,反斜杠后跟任何非字母字符意味着后一個字符。 因此,反斜杠后跟換行符......意味着與換行符相同。
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("\\\\","/")
的功能,這恰恰是矛盾的。
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
類,您可以獲得預期的結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.