文章目錄
1.什麼是正則表達式
正則表達式(Regular Expression)又稱正規表示法、常規表示法,在代碼中常簡寫爲 regex、regexp 或 RE,它是計算機科學的一個概念。
正則表達式是一個強大的字符串處理工具,可以對字符串進行查找、提取、分割、替換等操作,是一種可以用於模式匹配和替換的規範。一個正則表達式就是由普通的字符(如字符 a~z)以及特殊字符(元字符)組成的文字模式,它用以描述在查找文字主體時待匹配的一個或多個字符串。
2.在java裏如何使用正則表達式
String 類裏也提供瞭如下幾個特殊的方法。
boolean matches(String regex):判斷該字符串是否匹配指定的正則表達式。
String replaceAll(String regex, String replacement):將該字符串中所有匹配 regex 的子串替換成 replacement。
String replaceFirst(String regex, String replacement):將該字符串中第一個匹配 regex 的子串替換成 replacement。
String[] split(String regex):以 regex 作爲分隔符,把該字符串分割成多個子串。
matches的源碼聲明,可見是調用Pattern的matches方法。
上面這些特殊的方法都依賴於 Java 提供的正則表達式支持,除此之外,Java 還提供了 Pattern 和 Matcher 兩個類專門用於提供正則表達式支持。
其實正則表達式是一種非常簡單而且非常實用的工具。正則表達式是一個用於匹配字符串的模板。實際上,任意字符串都可以當成正則表達式使用
。例如“abc”,它也是一個正則表達式,只是它只能匹配“abc”字符串。
2.1 如何創建正則表達式(規則)
1 合法字符
創建正則表達式就是創建一個特殊的字符串
。正則表達式所支持的合法字符如表 1 所示。
2 特殊字符(限定符)
正則表達式中有一些特殊字符,這些特殊字符在正則表達式中有其特殊的用途,比如前面介紹的反斜線\
(\也是需要轉義的
)。也叫限定符。
正則表達式中允許使用限定修飾符來限定元字符出現的次數
如果需要匹配這些特殊字符,就必須首先將這些字符轉義
,也就是在前面添加一個反斜線\
。正則表達式中的特殊字符如表 2 所示。
樣式:
將上面多個字符拼起來,就可以創建一個正則表達式。例如:
"\u0041\\\\" // 匹配 A\
"\u0061\t" // 匹配a<製表符>
"\\?\\[" // 匹配?[
注意:由上代碼知正則表達式就是一字符串
可能大家會覺得第一個正則表達式中怎麼有那麼多反斜槓?這是由於 Java 字符串中反斜槓本身需要轉義,因此兩個反斜槓(\\)實際上相當於一個(前一個用於轉義)
。
3 預定義字符(元字符)
上面的正則表達式依然只能匹配單個字符,這是因爲還未在正則表達式中使用“通配符”,“通配符”是可以匹配多個字符的特殊字符。正則表達式中的“通配符”遠遠超出了普通通配符的功能,它被稱爲預定義字符,正則表達式支持如表 3 所示的預定義字符。
元字符 | 正則表達式的寫法 | 說明 |
---|---|---|
. | “.” | 代表任意一個字符 |
\d | “\\d” | 代表 0~9 的任何一個數字 |
\D | “\\D” | 代表任何一個非數字字符 |
\s | “\\s” | 代表空白字符,如"\t’’和’’\n” |
\S | “\\S” | 代表非空白字符 |
\W | “\\W” | 代表不可用於標識符的字符 |
\p {Lower} | \\p {Lower} | 代表小寫字母 {a~z} |
\p {Upper} | \\p {Upper} | 代表大寫字母 {A~Z} |
\p {ASCII} | \\p {ASCII} | ASCII 字符 |
\p {Alpha} | \\p {Alpha} | 字母字符 |
\p {Digit} | \\p {Digit} | 十進制數字,即 [0~9] |
\p {Alnum} | \\p {Alnum} | 數字或字母字符 |
\p {Punct} | \\p {Punct} | 標點符號:!"#$%&’()*+,-./:;<=>?@[]^_`{ |
\p {Graph} | \\p {Graph} | 可見字符:[\p{Alnum}\p{Punct}] |
\p {Print} | \\p {Print} | 可打印字符:[\p{Graph}\x20] |
\p {Blank} | \\p {Blank} | 空格或製表符:[\t] |
\p {Cntrl} | \\p {Cntrl} | 控制字符:[\x00-\x1F\x7F] |
在正則表達式中,可以使用方括號括起來若干個字符來表示一個元字符。這個元字符可以代表方括號中的任何一個字符
,例如字符串“reg=“a4”” “reg=“b4””和“reg=“c4””都是與“reg="[abc]4"”匹配的字符串。
最上面的 7 個預定義字符其實很容易記憶,其中:
- d 是 digit 的意思,代表數字。
- s 是 space 的意思,代表空白。
- w 是 word 的意思,代表單詞。
- d、s、w 的大寫形式恰好匹配與之相反的字符。
有了上面的預定義字符後,接下來就可以創建更強大的正則表達式了。例如:
c\\wt // 可以匹配cat、cbt、cct、cOt、c9t等一批字符串,\\w即\w,表示匹配任何單詞字符
\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d // 匹配如 000-000-0000 形式的電話號碼
4 方括號表達式
在一些特殊情況下,例如,若只想匹配 a~f 的字母,或者匹配除 ab 之外的所有小寫字母,或者匹配中文字符,上面這些預定義字符就無能爲力了,此時就需要使用方括號表達式
,方括號表達式有如表 4 所示的幾種形式。
方括號表達式比前面的預定義字符靈活多了,幾乎可以匹配任何字符。
例如,若需要匹配所有的中文字符,就可以利用 [\u0041-\u0056] 形式——因爲所有中文字符的 Unicode 值是連續的,只要找出所有中文字符中最小、最大的 Unicode 值,就可以利用上面形式來匹配所有的中文字符。
正則表達式還支持圓括號,用於將多個表達式組成一個子表達式,圓括號中可以使用或運算符|。例如,正則表達式“((public)|(protected)|(private))”
用於匹配 Java 的三個訪問控制符其中之一。
5 邊界匹配符
除此之外,Java 正則表達式還支持如表 5 所示的幾個邊界匹配符。
6 三種匹配模式
前面例子中需要建立一個匹配 000-000-0000 形式的電話號碼時,使用了 \d\d\d-\d\d\d-\d\d\d\d 正則表達式,這看起來比較煩瑣。實際上,正則表達式還提供了數量標識符,正則表達式支持的數量標識符有如下幾種模式。
- Greedy(
貪婪模式
):數量表示符默認採用貪婪模式
,除非另有表示。貪婪模式的表達式會一直匹配下去,直到無法匹配爲止。如果你發現表達式匹配的結果與預期的不符,很有可能是因爲你以爲表達式只會匹配前面幾個字符,而實際上它是貪婪模式,所以會一直匹配下去。 - Reluctant(
勉強模式
):用問號後綴(?)表示,它只會匹配最少的字符。也稱爲最小匹配模式。 - Possessive(
佔有模式
):用加號後綴(+)表示,目前只有 Java 支持佔有模式,通常比較少用。
三種模式的數量表示符如表 6 所示。
關於貪婪模式和勉強模式的對比,看如下代碼:
String str = "hello,java!";
// 貪婪模式的正則表達式
System.out.println(str.replaceFirst("\\w*" , "■")); //輸出■,java!
// 勉強模式的正則表達式
System.out.println(str.replaceFirst("\\w*?" , "■"")); //輸出■hello, java!
當從“hello java!”
字符串中查找匹配\\w*
子串時,因爲\w*使用了貪婪模式,數量表示符*會一直匹配下去,所以該字符串前面的所有單詞字符都被它匹配到,直到遇到空格
,所以替換後的效果是“■,Java!”;如果使用勉強模式,數量表示符*會盡量匹配最少字符,即匹配 0 個字符
,所以替換後的結果是“■hello,java!”。
3 Demo
3.1 簡單演示匹配電話與郵箱
需求:用戶輸入電話與郵箱地址,判斷其合法性。(電話是13或者15開頭的11位的移動電話或者固定電話;郵箱僅支持qq郵箱及139郵箱)
代碼演示:
package java基礎;
import java.util.Scanner;
/**
* @author Lh
* @version 1.1.0
* 需求:演示正則表達式匹配電話號碼及郵箱
*/
public class regex_Demo {
public static void main(String[]args){
//匹配固定電話或者移動電話
String regex_phone = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";
//匹配郵箱,分名稱,域名
String regex_email = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";//^匹配開頭,$匹配末尾
Scanner sc = new Scanner(System.in);
System.out.println("請輸入電話:");
String phone_number = sc.next();
System.out.println("請輸入郵箱:");
String emil_number = sc.next();
boolean result_p = phone_number.matches(regex_phone);
boolean result_e = emil_number.matches(regex_email);
if(result_p){
System.out.println("電話格式匹配成功!");
}
else{
System.out.println("電話格式匹配錯誤!");
}
if(result_e){
System.out.println("郵箱格式匹配成功!");
}
else{
System.out.println("郵箱格式匹配錯誤!");
}
}
}
正則表達式說明:
1.電話
電話號碼包括固定電話和手機號碼。其中固定電話是由區號和號碼組成,區號是以 0 開頭的,後面是 2~3 位數,因此在匹配區號的時候可以使用正則表達式0\d{2,3}。固定電話號碼由 7~8 位數字組成,因此可以使用表達式\d{7,8}來進行匹配。固定電話的組合方式可能是“區號-號碼”或者是“區號號碼”,因此匹配固定電話號碼時,可以使用“0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}”表達式。
手機號碼是 11 位數,並且以數字 1 開頭。考慮到手機號碼的特殊性,這裏使用“13[0-9]\\d{8}|15[1089]\\d{8}”表達式進行匹配。該正則表達式驗證以 13 或 15 開頭的手機號碼; 以 15 開頭的電話號碼,第 3 位數字只能是 1、0、8、9 中的一個。
2.郵箱
只允許英文字母、數字、下劃線、英文句號、以及中劃線組成
例如:[email protected](名稱+域名)
分析郵件名稱部分:
- 26個大小寫英文字母表示爲a-zA-Z
- 數字表示爲0-9
- 下劃線表示爲_
- 中劃線表示爲-
由於名稱是由若干個字母、數字、下劃線和中劃線組成,所以需要用到+表示多次出現
根據以上條件得出郵件名稱表達式:[a-zA-Z0-9_-]+
分析域名部分:
一般域名的規律爲“[N級域名][三級域名.]二級域名.頂級域名”,比如“qq.com”、“www.qq.com”、“mp.weixin.qq.com”、“12-34.com.cn”,分析可得域名類似“** .** .** .**”組成。
- “**”部分可以表示爲[a-zA-Z0-9_-]+
- “.**”部分可以表示爲.[a-zA-Z0-9_-]+
- 多個“.**”可以表示爲(.[a-zA-Z0-9_-]+)+
綜上所述,域名部分可以表示爲[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+
最終表達式:
由於郵箱的基本格式爲“名稱@域名”,需要使用“^”
匹配郵箱的開始部分,用“$”
匹配郵箱結束部分以保證郵箱前後不能有其他字符,所以最終郵箱的正則表達式爲:
^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
4 Pattern類和Matcher類的使用
java.util.regex 是一個用正則表達式所訂製的模式來對字符串進行匹配工作的類庫包。它包括兩個類:Pattern 和 Matcher。
Pattern 對象是正則表達式編譯後在內存中的表示形式,因此,正則表達式字符串必須先被編譯爲 Pattern 對象,然後再利用該 Pattern 對象創建對應的 Matcher 對象
。執行匹配所涉及的狀態保留在 Matcher 對象中,多個 Matcher 對象可共享同一個 Pattern 對象。
因此,典型的調用順序如下:
// 將一個字符串編譯成 Pattern 對象,使用compile()
Pattern p = Pattern.compile("a*b");
// 使用 Pattern 對象創建 Matcher 對象
Matcher m = p.matcher("aaaaab");
boolean b = m.matches(); // 返回 true
上面定義的 Pattern 對象可以多次重複使用。如果某個正則表達式僅需一次使用,則可直接使用 Pattern 類的靜態 matches() 方法,此方法自動把指定字符串編譯成匿名的 Pattern 對象,並執行匹配,如下所示。
boolean b = Pattern.matches ("a*b","aaaaab"); // 返回 true
上面語句等效於前面的三條語句。但採用這種語句每次都需要重新編譯新的 Pattern 對象,不能重複利用已編譯的 Pattern 對象,所以效率不高。
Pattern 是不可變類,可供多個併發線程安全使用。
Matcher 類提供了幾個常用方法,如表 1 所示。
在 Pattern、Matcher 類的介紹中經常會看到一個 CharSequence 接口,該接口代表一個字符序列,其中 CharBuffer、String、StringBuffer、StringBuilder 都是它的實現類。簡單地說,CharSequence 代表一個各種表示形式的字符串。
通過 Matcher 類的 find() 和 group() 方法可以從目標字符串中依次取出特定子串(匹配正則表達式的子串),例如互聯網的網絡爬蟲,它們可以自動從網頁中識別出所有的電話號碼。下面程序示範瞭如何從大段的字符串中找出電話號碼。
package java基礎;
import java.util.regex.Matcher;
import java.util.regex.Pattern;//注意要導入類庫
public class FindPhone {
public static void main(String[] args) {
// 使用字符串模擬從網絡上得到的網頁源碼
String str = "我想找一套適合自己的JAVA教程,儘快聯繫我13500006666" + "交朋友,電話號碼是13611125565" + "出售二手電腦,聯繫方式15899903312";
// 創建一個Pattern對象,並用它建立一個Matcher對象
// 該正則表達式只抓取13X和15X段的手機號
// 實際要抓取哪些電話號碼,只要修改正則表達式即可
Matcher m = Pattern.compile("((13\\d)|(15\\d))\\d{8}").matcher(str);
// 將所有符合正則表達式的子串(電話號碼)全部輸出
while (m.find()) {
System.out.println(m.group());
}
}
}
運行結果:
從上面運行結果可以看出,find() 方法依次查找字符串中與 Pattern 匹配的子串,一旦找到對應的子串,下次調用 find() 方法時將接着向下查找。
提示:通過程序運行結果可以看出,使用正則表達式可以提取網頁上的電話號碼,也可以提取郵件地址等信息。如果程序再進一步,可以從網頁上提取超鏈接信息,再根據超鏈接打開其他網頁,然後在其他網頁上重複這個過程就可以實現簡單的網絡爬蟲了。
find() 方法還可以傳入一個 int 類型的參數,帶 int 參數的 find() 方法將從該 int 索引處向下搜索。start() 和 end() 方法主要用於確定子串在目標字符串中的位置
,如下程序所示。
public class StartEnd {
public static void main(String[] args) {
// 創建一個Pattern對象,並用它建立一個Matcher對象
String regStr = "Java is very easy!";
System.out.println("目標字符串是:" + regStr);
Matcher m = Pattern.compile("\\w+").matcher(regStr);
while (m.find()) {
System.out.println(m.group() + "子串的起始位置:" + m.start() + ",其結束位置:" + m.end());
}
}
}
上面程序使用 find()、group() 方法逐項取出目標字符串中與指定正則表達式匹配的子串,並使用 start()、end() 方法返回子串在目標字符串中的位置。運行上面程序,看到如下運行結果:
目標字符串是:Java is very easy!
Java子串的起始位置:0,其結束位置:4
is子串的起始位置:5,其結束位置:7
very子串的起始位置:8,其結束位置:12
easy子串的起始位置:13,其結束位置:17
matches() 和 lookingAt() 方法有點相似,只是 matches() 方法要求整個字符串和 Pattern 完全匹配時才返回 true,而 lookingAt() 只要字符串以 Pattern 開頭就會返回 true。reset() 方法可將現有的 Matcher 對象應用於新的字符序列。看如下例子程序。
public class MatchesTest {
public static void main(String[] args) {
String[] mails = { "[email protected]", "[email protected]", "[email protected]", "[email protected]" };
String mailRegEx = "\\w{3,20}@\\w+\\.(com|org|cn|net|gov)";
Pattern mailPattern = Pattern.compile(mailRegEx);
Matcher matcher = null;
for (String mail : mails) {
if (matcher == null) {
matcher = mailPattern.matcher(mail);
} else {
matcher.reset(mail);
}
String result = mail + (matcher.matches() ? "是" : "不是") + "一個有效的郵件地址!";
System.out.println(result);
}
}
}
上面程序創建了一個郵件地址的 Pattern,接着用這個 Pattern 與多個郵件地址進行匹配。當程序中的 Matcher 爲 null 時,程序調用 matcher() 方法來創建一個 Matcher 對象,一旦 Matcher 對象被創建,程序就調用 Matcher 的 reset() 方法將該 Matcher 應用於新的字符序列。
從某個角度來看,Matcher 的 matches()、lookingAt() 和 String 類的 equals() 有點相似。區別是 String 類的 equals() 都是與字符串進行比較,而 Matcher 的 matches() 和 lookingAt() 則是與正則表達式進行匹配。
事實上,String 類裏也提供了 matches() 方法,該方法返回該字符串是否匹配指定的正則表達式。例如:
"[email protected]".matches("\\w{3,20}@\\w+\\.(com|org|cn|net|gov)"); // 返回 true
除此之外,還可以利用正則表達式對目標字符串進行分割、查找、替換等操作,看如下例子程序。
public class ReplaceTest {
public static void main(String[] args) {
String[] msgs = { "Java has regular expressions in 1.4", "regular expressions now expressing in Java",
"Java represses oracular expressions" };
Pattern p = Pattern.compile("re\\w*");
Matcher matcher = null;
for (int i = 0; i < msgs.length; i++) {
if (matcher == null) {
matcher = p.matcher(msgs[i]);
} else {
matcher.reset(msgs[i]);
}
System.out.println(matcher.replaceAll("哈哈:)"));
}
}
}
上面程序使用了 Matcher 類提供的 replaceAll() 把字符串中所有與正則表達式匹配的子串替換成“哈哈:)”
實際上,String 類的replaceAll()、replaceFirst()、split() 等方法就是進行正則表達式匹配的。
正則表達式是一個功能非常靈活的文本處理工具,增加了正則表達式支持後的 Java,可以不再使用 StringTokenizer 類(也是一個處理字符串的工具,但功能遠不如正則表達式強大)即可進行復雜的字符串處