目錄
正則表達式作為文本處理的利器,早已成為各大語言的必要裝備,但各語言對其的實現程度(功能語法支持程度)和API設計均有所差異,本篇將目光投向java原生類庫中提供的正則表達式API—— java.util.regex包 和 java.lang.String實例方法 ,和所支持的功能語法上。
正則表達式一般用于處理如下訴求,本篇后續內容將以這些訴求為基礎檢驗相關的原生API是否提供方便有效(code less,do more)的操作方式。
1. 匹配字符串:全字符串匹配、部分匹配(也就是包含關系)
2. 替換字符串
3. 萃取字符串
4. 拆分字符串
從jdk1.5開始正則表達式相關API就集中存放在該包下,且為其他類中表達式相關方法提供基礎處理能力。
1. java.util.regex.Pattern類 :模式類,用于對正則表達式進行編譯。
類方法:
/* * 對正則表達式進行編譯,并返回Pattern實例 * 入參flag作為表達式屬性,啟用多個表達式屬性時,采用管道符(|)連接多個表達式屬性。除了通過入參的方式設置表達式屬性,還可以使用嵌入式標識來設置表達式屬性,格式為:(?表達式屬性1表達式屬性2)正則表達式,示例——不區分大小寫和全局匹配abcd:(?ig)abcd */ Pattern compile(String regex); Pattern compile(String regex, int flag); // 字符串完整匹配表達式的快捷方式,內部依然是 // Pattern p = Pattern.compile(regex); // p.matcher(input).matches(); boolean matches(String regex, CharSequence input); // 返回可以配置入參s的字面量模式。注意格式為\\Q表達式\\E。表達式中的元字符將當作普通字符處理 String quote(String s);
表達式屬性:
// 以\n作為換行符,內嵌為(?d) Pattern.UNIX_LINES // US-ASCII編碼字符不區分大小寫,內嵌為(?i) Pattern.CASE_INSENSITIVE // 忽略空格和注釋(注釋為以#開頭直到出現換行符),內嵌為(?x) Pattern.COMMENTS // 啟動多行模式,^和$匹配換行符或字符串起始位置。默認為單行模式,^和$僅匹配字符串起始位置。內嵌為(?m) Pattern.MULTILINE // 字面量模式,將元字符當作普通字符處理,沒有內嵌方式,但可以通過"\\Q正則表達式\\E"的方式實現 Pattern.LITERAL // 元字符.將匹配換行符。默認情況下,元字符.不匹配換行符。內嵌為(?s) Pattern.DOTALL // UNICODE編碼字符不區分大小寫,內嵌為(?u) Pattern.UNICODE_CASE // 當且僅當正則分解匹配時才配置成功。 Pattern.CANON_EQ // 啟用Unicode版本的預定義字符類和POSIX字符類,內嵌為(?U) Pattern.UNICODE_CHARACTER_CLASS
實例方法:
// 返回正則表達式 String pattern(); // 使用正則表達式匹配的字符串切割入參input // 入參limit用于設置返回數組長度的最大值,設置為0時則不限制最大值。 String[] split(CharSequence input); String[] split(CharSequence input, int limit); // 獲取匹配類 Matcher matcher(CharSequence input);
2. java.util.regex.Matcher類 :匹配類,用于存儲模式實例匹配某字符串后所產生的結果。
靜態方法:
// 將入參s中的\和$元字符轉換為普通字符,并返回處理后的s字符串。 String quoteReplacement(String s)
實例方法:
// 獲取匹配子字符串的起始索引 int start(); // 獲取匹配子字符串的結束索引 int end(); // 從字符串的end+1位置開始搜索下一個匹配的字符串 boolean find(); boolean find(int start); // 通過分組索引獲取分組內容,若入參group超出分組數量則拋異常 String group(); String group(int group); // 通過分組名稱獲取分組內容,若沒有相應的分組則返回null String group(String name); // 重置匹配實例內部的狀態屬性 Matacher reset(); // 重置匹配實例內部的狀態屬性,并重置被匹配的字符串 Matacher reset(CharSequence input); // 重置模式實例,這導致group信息丟失,但注意:start等信息依舊保留不變。 Matcher usePattern(Pattern newPattern); // 從字符串起始位開始將匹配成功的子字符串均用入參replacement替換掉 String replaceAll(String replacement); // 從字符串起始位開始將第一個匹配成功的子字符串均用入參replacement替換掉 String replaceFirst(String replacement); // 將從字符串起始位開始到最后一匹配的子字符串最后一個字符的位置的字符串復制到sb中,并用入參replacement替換sb中匹配的內容 String appendReplace(StringBuffer sb, String replacement); // 將剩余的子字符串復制到sb中 String appendTail(StringBuffer sb); // 示例: sb為one dog two dog Matcher m = p.matcher("one cat two cats in the yard"); StringBuffer sb = new StringBuffer(); while (m.find()) { m.appendReplacement(sb, "dog"); } // 字符串從頭到尾匹配表達式 boolean matches(); // 從字符串起始位置開始匹配表達式,但不要字符串從頭到尾匹配表達式 boolean lookingAt();
實例方法:
String replaceAll(String replacement); String replaceFirst(String replacement); String[] split(String regex); String[] split(String regex, int limit); boolean matches(String regex)
五、最短路徑實現訴求
final class RegExp{ // 全字符串匹配 public static boolean isMatch(String regex, String input){ if (null == input) return false; return input.matches(regex); } // 包含子字符串 public static boolean contains(String regex, String input){ Pattern r = Pattern.compile(regex); return r.matcher(input).find(); } // 實現indexOf public static int indexOf(String regex, String input){ Pattern r = Pattern.compile(regex); Matcher m = r.matcher(input); int index = -1; if(m.find()) index = m.start(); return index; } // 實現lastIndexOf public static int lastIndexOf(String regex, String input){ Pattern r = Pattern.compile(regex); Matcher m = r.matcher(input); int index = -1; while(m.find()) index = m.start(); return index; } // 替換全部匹配字符串 public static String replaceAll(String regex, String input, String replacement){ if (null == regex || regex.isEmpty()) return input; return input.replaceAll(regex, replacement); } // 替換第N個匹配字符串 public static String replaceSome(String regex, String input, String replacement, int n){ if (null == regex || regex.isEmpty()) return input; if (0 == n) return input.replaceFirst(regex, replacement); Pattern r = Pattern.compile(regex); Matcher m = r.matcher(input); int i = 0; StringBuffer buffer = new StringBuffer(); while (i <= n && m.find()){ if (i == n){ m.appendReplacement(buffer, replacement); m.appendTail(buffer); } ++i; } if (0 == buffer.length()) buffer.append(input); return buffer.toString(); } // 萃取字符串 public static String extract(String regex, String input){ String ret = ""; Pattern r = Pattern.compile(regex); Matcher m = r.matcher(input); if (m.find()) ret = m.group(); return ret; } // 拆分字符串 public static String[] split(String regex, String input, int limit){ if (null == input || input.isEmpty() || null == regex || regex.isEmpty()) return new String[]{input}; return input.split(regex, limit); } }
實際應用時當然不會像上面那么簡單了。
本節內容僅針對正則表達式的高級功能語法進行敘述,而各語言的正則實現也就是這部分有所差異而已。
1. 分組及反向引用
[a]. (子表達式) ,自動命名分組(從1開始以數字自動為分組命名),后續表達式中可通過反向引用來獲取該分組的內容。例如匹配字符串“so so”的正則表達式可以是 ^(\w{2})\s(\1)$ ,其中 \1 就是反向引用。
[b]. (?:子表達式) ,非捕獲分組,該類型的分組將不納入匹配對象的group屬性中,并且無法通過反向引用在表達式的后續部分獲取該分組的內容。通常是配合 | 使用。例如匹配字符串"so easy"和"so hard"的正則表達式可以是 so\s(?:easy|hard)
[c]. (?<name>子表達式) ,命名分組,該類型的分組將納入匹配對象的group屬性中,并且可以在group屬性值中通過name值來獲取該分組的值。
[d]. (?#注釋) ,注釋分組,該類型分組的內容將被正則表達式編譯器忽略,僅供碼農查閱而已。
2. 零寬先行斷言
零寬先行斷言初看之下有點不知所云的感覺, 那么我們拆開來分析一下它的意思吧!
零寬——意思是匹配的子表達式將不被納入匹配結果,僅作為匹配條件而已。
先行——意思是子表達式匹配的是后續字符串的內容。
并且其細分為兩類:
[a]. 子表達式B(?=子表達式A) ,零寬正向先行斷言(也稱為預搜索匹配)。例如匹配字符串"abcd"中的a和b的正則表達式可以是 \w(?=\w{2})
[b]. 子表達式B(?!子表達式A) ,零寬負向先行斷言(也稱為預搜索不匹配)。例如匹配字符串"abcd"中的c和d的正則表達式可以是 \w(?!\w{2})
3. 零寬后行斷言
后行——意思是子表達式匹配的是前面字符串的內容。
[a]. (?<=子表達式A)子表達式B ,零寬正向后行斷言(也稱為反向搜索匹配)。例如匹配字符串"abcd"中的c和d的正則表達式可以是 (?<=\w{2})\w
[b]. (?<!子表達式A)子表達式B ,零寬負向后行斷言(也稱為反向搜索不匹配)。例如匹配字符串"abcd"中的a和b的正則表達式可以是 (?<!\w{2})\w
4. 平衡組
作用:用于匹配左右兩邊開始、結束符號數量對等的字符串。
示例——萃取"<div>parent<div>child</div></div></div>"的子字符串"<div>parent<div>child</div></div>"
失敗的正則表達式: <div>.*</div> ,匹配結果為"<div>parent<div>child</div></div></div>"。
成功的正則表達式: ((?'g'<div>).*?)+(?'-g'</div>)+ ,匹配結果為"<div>parent<div>child</div></div>"。
在分析上述示例前,我們要認識一下平衡組相關的語法。
(?'name'子表達式A) ,若成功匹配子表達式A,則往名為name的棧空間壓一個元素。
(?'-name'子表達式A) ,若成功匹配子表達式A,則彈出名為name的棧空間的棧頂元素,彈出元素后若棧空間為空則結束匹配。
(?(name)yes表達式|no表達式) ,若名為name的棧空間非空,則使用yes表達式進行匹配,否則則使用no表達式進行匹配。
(?(name)yes表達式) ,若名為name的棧空間非空,則使用yes表達式進行匹配。
(?!) ,由于沒有后綴表達式,因此總會導致匹配失敗并結束匹配。
下面我們一起來分析 ((?'g'<div>).*?)+(?'-g'</div>)+ 的匹配流程吧!
<div>parent # 步驟1,((?'g'<div>).*?)匹配成功,然后向g棧壓入一個元素 <div>child # 步驟2,((?'g'<div>).*?)匹配成功,然后向g棧壓入一個元素,現在棧含2個元素 </div> # 步驟3,(?'-g'</div>)匹配成功,然后彈出g棧的棧頂元素,現在棧含1個元素 </div> # 步驟4,(?'-g'</div>)匹配成功,然后彈出g棧的棧頂元素,現在棧含0個元素 # 步驟5,由于g棧為空因此結束匹配,返回<div>parent<div>child</div></div>
從該例子我們可以知道平衡組可以解決一些棘手的文本處理問題。但遺憾的是直到JDK1.7的原生API依舊不支持平衡組的功能語法,其余功能語法均被支持。而.Net的Regex類則支持平衡組,在這方面顯然全面一些。當然比js連零寬后行斷言都不支持要強不少了。
2015/10/30追加
注意:若正則表達式僅含/()/、/(?:)/或/(?=)/,則匹配任何字符串均返回匹配成功,且配結果為空字符串。而JS中 RegExp('') 所生成的是無捕獲分組 /(?:)/ 。
而僅含/(?!)/,則匹配任務字符串均返回匹配失敗。
console.log(RegExp('').test("12345")) // 顯示true console.log((?:)/.test("12345")) // 顯示true console.log(/(?=)/.test("12345")) // 顯示true console.log(/()/.test("12345")) // 顯示true console.log(/(?!)/.test("12345")) // 顯示false
到這里我們已經對Java對正則表達式的支持程度有一定程度的掌握,雖然不支持平衡組但已經為我們提供強大的文本處理能力了。不過我依舊不滿意那個礙眼的轉義符 \ ,假如我們要寫正則表達式 \w\\\{\} 但實際運用時卻要寫成 \\w\\\\\\{\\} ,倘若能夠像JS的正則表達式字面量一樣使用,那就舒暢不少了!
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/4098623.html ^_^肥仔John
http://deerchao.net/tutorials/regex/regex-1.htm
http://www.cnblogs.com/kissdodog/archive/2013/04/25/3043122.html
文章列表