背景:
前幾天有人發了一個關於下面問題的貼,對這個有點好奇,故花時間作了點研究。
對單個反斜槓字符串替換成雙斜槓的Java實現以下:
String s = "\\";
方法一:String sr1 = s.replaceAll("\\\\", "\\\\\\\\");
方法二:String sr1 = s.replaceAll("\\\\", "$0$0");
我第一眼看到比較困惑,下面慢慢來分析。
分析:
對String類的replaceAll(String reg, String replacement)方法分析
1、兩點疑惑
A. 爲啥第一個參數reg必須是」\\\\」?
B. 爲啥第二個參數replacement 必須是」\\\\\\\\」?
2、解答
A.由於reg這個參數表示一個正則表達式,首先字符串「\\\\」被轉義後表明的實際是字符串\\,這就是正則表達式,那麼在正則表達式裏也有轉義,那麼這個正則匹配的就是\
B.首先字符串「\\\\\\\\」被轉義後實際表明的實際上是字符串\\\\;
接下來纔是重點:
查看源碼replaceAll方法的實現以下
java
public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); }
其中Pattern.compile(regex).matcher(this), 返回的是一個Matcher對象。
先簡單介紹java.util.regex.Matcher類,是經過解釋 Pattern 對 字符序列執行匹配操做的引擎,其中持有對當前Pattern對象和當前String對象的引用。
執行一次調用其find方法,即對字符串執行一次從左向右的以Pattern爲正則的匹配,並記錄下匹配結果字符串的開始和結束位置索引,以及更新一個記錄當前匹配結果的分組groups。
順藤摸瓜,進入Matcher類的replaceAll方法,繼續查看源碼,
正則表達式
public String replaceAll(String replacement) { // 對當前Matcher類進行重置,即對其中記錄匹配結果的開始和結束位置索引,以及分組信息重置 reset(); // 執行第一次搜索 boolean result = find(); if (result) { // 第一次搜索匹配成功 // 用於記錄最終的替換結果字符串 StringBuffer sb = new StringBuffer(); // 循環搜索 do { // *重點在此方法內:用於將從上一次匹配子字符串的下一個索引位置開始,到當前匹配的子字符串的結束索引位置的全部字符 append到字符串sb中 // 有點繞,能夠暫時跳過,下面會對該方法進一步分析 appendReplacement(sb, replacement); result = find(); } while (result); // 將從最後一次匹配子字符串的下一個索引位置,到字符串的結尾的全部字符append到字符串sb中 appendTail(sb); return sb.toString(); } return text.toString(); }
繼續南下,進入Matcher類的appendReplacement方法,
api
public Matcher appendReplacement(StringBuffer sb, String replacement) { ...省略部分代碼 // 用於跟蹤replacement字符串的索引 int cursor = 0; String s = replacement;// Java api源碼也有垃圾代碼啊,呵呵 (s局部變量並未在後續代碼中被使用) // 對當前匹配到子字符串替換後的結果字符串 StringBuffer result = new StringBuffer(); // 遍歷replacement字符串 while (cursor < replacement.length()) { char nextChar = replacement.charAt(cursor); if (nextChar == '\\') { // 重點1:當字符爲\時,跳過,並獲取其後面的字符,追加到result cursor++; nextChar = replacement.charAt(cursor); result.append(nextChar); cursor++; } else if (nextChar == '$') { // 重點2:當字符爲$時,跳過,並獲取其後面的數值,並以此若是$後面第一個不爲數字則拋異常, // Skip past $ cursor++; // The first number is always a group int refNum = (int)replacement.charAt(cursor) - '0'; // 此處代碼用於計算$符號後的數值,數值結果賦予refNum ...省略部分代碼 // group(refNum) 用於獲取正則表達式第refNum個分組表示的字符串,不詳說了 if (group(refNum) != null) result.append(group(refNum)); // 追加到result } else { // 當前字符不爲\ 或 $ 則直接追加到result result.append(nextChar); cursor++; } } // 將從上一次匹配的子字符串的結尾索引,到當前匹配的第一個字符串索引的字符串追加到sb // lastAppendPosition參數爲上一次執行appendReplacement方法最後追加的字符在原始字符串中的索引位置。 // first 參數爲當前待替換的子字符串的首個字符在原始字符串中的索引位置 sb.append(getSubSequence(lastAppendPosition, first)); // 將當前配置子字符串替換後的結果字符串追加到sb sb.append(result.toString()); // 更新lastAppendPosition,供下一個匹配執行appendReplacement方法使用 lastAppendPosition = last; /* 到此, sb中追加了當前匹配的子字符串與前一次匹配子字符串中間的字符,以及當前匹配子字符串被替換後的字符串 */ return this; }
分析結束。
總結
一、replaceAll中第二個參數replacement中,\有轉義的做用,$用於獲取分組匹配的當前子字符串
如今想一想爲何要引入這個\轉義的功能? 個人猜想是 ----- 由於引入了$符的分組功能,因此爲了解決能輸出$字符,故引入\轉義功能
二、有助於理解Java的正則表達式;
三、世界上沒有十全十美的代碼,Java源碼裏也有垃圾代碼,呵呵。
提供幾個問題你們能夠實踐下:
一、對兩個反斜槓字符串每一個字符串都替換成雙斜槓,如何實現?
即String s = "\\\\"; 替換成 String sr = "\\\\\\";
二、將單反斜槓替換成美圓符,如何實現?
即String s = "\\"; 替換成 String sr = "$";
三、String s = "Jack is Rose's boyfriends."
使用$分組替換功能 替換成
String sr = "Rose is Jack's girlfriends."
app