目錄 html
1、前言 java
2、正則表達式的使用訴求 正則表達式
5、最短路徑實現訴求 less
7、總結 spa
8、參考 .net
正則表達式做爲文本處理的利器,早已成爲各大語言的必要裝備,但各語言對其的實現程度(功能語法支持程度)和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)
5、最短路徑實現訴求
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連零寬後行斷言都不支持要強很多了。
到這裏咱們已經對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