正则表达式-进阶
正則表達式-進階
文章目錄
- 正則表達式-進階
- 前言
- 探囊取物
- 移花接木
- 驀然回首
- 最終考驗
前言
看到這里,意味著你已經掌握了正則表達式的基礎知識,能夠運用正則解決一些簡單的問題了。如果你不熟悉正則的基礎知識,請參考前一篇博客學習。正則表達式-基礎
這部分我們將繼續探究正則的進階知識。
探囊取物
我們來看一個例子,請嘗試用正則表達式匹配出其姓名和年齡。
Name:Aurora Age:18 里面夾雜著一些無關緊要的數據 Name:Bob Age:20 數據有很多種錯誤的格式 Name:Cassin Age:22我們用正則的基礎知識來嘗試匹配,\w 匹配名字,\s 匹配空白,\d 匹配年齡。匹配的表達式為:Name:\w+\s*Age:\d{1,3}
System.out.println("Name:Aurora Age:18".matches("Name:\\w+\\s*Age:\\d{1,3}")); //輸出為true System.out.println("里面有一些無關緊要的數據".matches("Name:\\w+\\s*Age:\\d{1,3}")); //輸出為false System.out.println("Name:Bob Age:20".matches("Name:\\w+\\s*Age:\\d{1,3}")); //輸出為true System.out.println("數據有很多種錯誤的格式".matches("Name:\\w+\\s*Age:\\d{1,3}")); //輸出為false System.out.println("Name:Cassin Age:22".matches("Name:\\w+\\s*Age:\\d{1,3}")); //輸出為true既然已經匹配到了姓名和年齡的數據,那么我們就要將它們的內容取出來。這里我們使用 Java 語言的 indexof 和 subString 方法可以做到。
String str="Name:Aurora Age:18"; int begin=str.indexOf("Name"); int end=str.indexOf(' '); int begin2=str.indexOf("Age");String s1=str.substring(begin+5, end); String s2=str.substring(begin2+4);System.out.println(s1); //輸出 Aurora System.out.println(s2); //輸出 18實際上,正則擁有著更為便捷的取值方式。
我們只要用 ( ) 將需要取值的地方括起來,傳給 Pattern 對象,再用 Pattern 對象匹配后獲得的 Matcher 對象來取值就行了。每個匹配的值將會按照順序保存在 Matcher 對象的 group 中。
判斷 Pattern 對象與字符串是否匹配的方法是 Matcher.matches(),如果匹配成功,這個函數將返回 true,如果匹配失敗,則返回 false。
Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})"); Matcher matcher = pattern.matcher("Name:Aurora Age:18"); if(matcher.matches()){String s1 = matcher.group(1); String s2 = matcher.group(2);System.out.println(s1); //輸出 AuroraSystem.out.println(s2); //輸出 18 }至于說 group 的下標為什么不是從0,而是從1開始,是因為 group(0) 被用來保存整個字符串了。
System.out.println(matcher.group(0)); //輸出 Name:Aurora Age:18到這里你可能會犯迷糊,我們之前一直使用的是 String.matches 方法來匹配正則表達式,這里的 Pattern 又是什么呢?別著急,我們一步一步往下說。
我們首先來看下 String.matches 方法的源代碼。
public boolean matches(String regex) {return Pattern.matches(regex, this); }我們看到,該源代碼中調用了 Pattern.matches 方法,我們繼續跟進查看源代碼。
public static boolean matches(String regex, CharSequence input) {Pattern p = Pattern.compile(regex);Matcher m = p.matcher(input);return m.matches(); }我們可以看到,String.matches 方法的內部就是調用 Pattern 。而且,每次調用 String.matches 函數,都會新建出一個 Pattern 對象。
如果我們要使用同一個正則表達式多次匹配字符串的話,最佳做法是先新建一個 Pattern 對象,這樣可以反復使用,提高程序運行效率。
Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})"); Matcher matcher1 = pattern.matcher("Name:Aurora Age:18"); Matcher matcher2 = pattern.matcher("Name:Bob Age:20"); Matcher matcher3 = pattern.matcher("Name:Cassin Age:22");移花接木
考慮一個實際場景:你有一個讓用戶輸入便簽的輸入框,用戶可以輸入多個標簽,但是你并沒有提示用戶,標簽之間應該用什么間隔符號隔開。
這種情況下,用戶的輸入是五花八門的,會用空格,逗號,分號等一系列分隔符。例如:
- 二分,回溯,遞歸,分治
- 搜索;查找;旋轉;遍歷
- 數論 圖論 邏輯 概率
一般的做法是使用 String.split 方法,依次嘗試各種分割符號來解決這個問題。
public static String[] splitTabs(String tabs) {if(tabs.split(",").length==4) return tabs.split(",");if(tabs.split(";").length==4) return tabs.split(";");if(tabs.split(" ").length==4) return tabs.split(" ");return new String[0]; } public static void main(String[] args) {System.out.println(Arrays.toString(splitTabs("二分,回溯,遞歸,分治")));System.out.println(Arrays.toString(splitTabs("搜索;查找;旋轉;遍歷")));System.out.println(Arrays.toString(splitTabs("數論 圖論 邏輯 概率"))); }輸出為: [二分, 回溯, 遞歸, 分治] [搜索, 查找, 旋轉, 遍歷] [數論, 圖論, 邏輯, 概率]這種方法簡單粗暴,我們可以用正則表達式做到更好。
實際上,split 函數傳入的參數就是一個正則表達式。如果直接使用某字符串,就屬于精確匹配了,只能匹配那一個字符串。我們應該使用正則表達式的模糊匹配,只要能匹配成功,就將其分割。
System.out.println(Arrays.toString("二分,回溯,遞歸,分治".split("[,;\\s+]"))); System.out.println(Arrays.toString("搜索;查找;旋轉;遍歷".split("[,;\\s+]"))); System.out.println(Arrays.toString("數論 圖論 邏輯 概率".split("[,;\\s+]")));輸出為: [二分, 回溯, 遞歸, 分治] [搜索, 查找, 旋轉, 遍歷] [數論, 圖論, 邏輯, 概率]字符串中,不僅 split 函數,replaceAll 函數也是傳的正則表達式。我們可以用正則表達式進行模糊匹配,將符合規則的字符串全部替換掉。
上面的例子中,我們可以把用戶輸入的所有數據統一規范為使用 ; 分割。
System.out.println("二分,回溯,遞歸,分治".replaceAll("[,;\\s+]",";")); System.out.println("搜索;查找;旋轉;遍歷".replaceAll("[,;\\s+]",";")); System.out.println("數論 圖論 邏輯 概率".replaceAll("[,;\\s+]",";"));輸出為: 二分;回溯;遞歸;分治 搜索;查找;旋轉;遍歷 數論;圖論;邏輯;概率在 replaceAll 的第二個參數中,我們還可以通過 $1,$2,…來反向引用匹配到的子串。只要將需要引用的部分用 ( ) 括起來就可以了。
System.out.println("二分,回溯,遞歸,分治".replaceAll("([,;\\s+])","---$1")); System.out.println("搜索;查找;旋轉;遍歷".replaceAll("([,;\\s+])","$1+++")); System.out.println("數論 圖論 邏輯 概率".replaceAll("([,;\\s+])","***$1***"));輸出為: 二分---,回溯---,遞歸---,分治 搜索;+++查找;+++旋轉;+++遍歷 數論*** ***圖論*** ***邏輯*** ***概率有時候我們不需要替換,只需要將正則匹配出來的部分添加一些前綴或后綴,就可以用這種方式!
驀然回首
給你一串字符串,統計尾數 e 的個數:
- LeetCode
- LeetCodeeeee
- LeetCodeee
這看起來并不難,結合我們所學的知識,使用 (\w+)(e*) 匹配,再用 group(2) 判斷即可。
Pattern pattern = Pattern.compile("(\\w+)(e*)"); Matcher matcher = pattern.matcher("LeetCode"); if(matcher.matches()) {String s1 = matcher.group(1);String s2 = matcher.group(2);System.out.println("s1= "+s1+", s1.length= "+s1.length());System.out.println("s2= "+s2+", s2.length= "+s2.length()); }輸出為: s1= LeetCode, s1.length= 8 s2= , s2.length= 0我們原本期望的是 s1=LeetCod,s2=e,但是結果好像并不和我們想的一樣。
還記得我們在基礎部分提到的貪婪模式和非貪婪模式嗎。這個例子就是因為默認使用的貪婪匹配, e 仍然屬于 \w 能匹配的范疇,正則表達式默認會盡可能多地向后匹配,所以 LeetCode 全部被 s1匹配完了。
非貪婪匹配的定義是在能匹配目標字符串的前提下,盡可能少的向后匹配。
貪婪匹配要更改為非貪婪匹配也很簡單,只需要非貪婪匹配的正則表達式后面加個 ? 即可表示非貪婪匹配。
Pattern pattern = Pattern.compile("(\\w+?)(e*)"); Matcher matcher = pattern.matcher("LeetCode"); if(matcher.matches()) {String s1 = matcher.group(1);String s2 = matcher.group(2);System.out.println("s1= "+s1+", s1.length= "+s1.length());System.out.println("s2= "+s2+", s2.length= "+s2.length()); }輸出為: s1= LeetCod, s1.length= 7 s2= e, s2.length= 1我們第一次介紹 ?時,? 表示的是匹配 0 次或者 1 次,而非貪婪匹配要使用 ?,會不會出現符號混淆的問題呢?不用擔心,這個問題不會出現。
- 如果只有一個字符,那就不存在貪婪不貪婪的問題
- 如果匹配多次,那么表示非貪婪匹配的 ? 前面必有一個標志匹配次數的符號
上面的這個例子中,不會出現 s1=L,s2=ee 的情況。如果這樣匹配的話,字符串 LeetCode 就無法和正則表達式匹配起來了。
最終考驗
最后出一道題來測試一下,如果通過了它,相信你應該學會了正則的知識。
有一個人說話不利索,經常口吃,請你幫忙糾正他。
肚…子。。好餓…,…早知道…當…初…。。。多…刷…點。。。力…扣了…!
String str="肚...子。。好餓........,....早知道.....當.....初...。。。多.....刷.....點。。。力.....扣了........."; String str2=str.replaceAll("[\\.。]",""); System.out.println(str2);輸出為: 肚子好餓,早知道當初多刷點力扣了記住,實踐出真知,多多練習,熟能生巧。
總結