重新釋出於2020年09月27日,寫於2016年
看了好些天的正規表示式,終於有時間來寫一篇關於它的部落格了。也是因為前段時間做標籤處理的工作用到,用正則匹配標籤規則,少寫了不少程式碼。在有的地方使用正規表示式確實特別棒。參考博文http://blog.csdn.net/yaerfeng/article/details/28855587 ,文中提到程式設計師的七種基本技能,確實各種語言,系統裡幾乎都有對正則的支援,雖說不用精通,但也要基本運用沒問題。
元字元
元字元標識在正規表示式中有特殊含義的字元,正是由它們,正規表示式才真正存在。JAVA中支援的元字元列表有:([{\^-$|}])?*+.
(
)
:正則組中使用[
]
:字元類中表示一個字元{
}
:數量範圍標識\
:預定義字元類中使用^
$
:邊界標識-
:字元類中表示某個範圍時使用,和"[]"配合使用|
:邏輯或?
*
+
:預定義數量詞^
:邏輯非.
:點號匹配除換行符的任意字元 (這個地方任然有點疑問)
這裡要特別說明一個符號&
,雖然&&
在字元類
中扮演著邏輯與
運算,但卻不在元字元行列中
檢測工具
為了學習簡單,寫了一段測試程式碼做檢測用,當然你也可以使用網上的檢測工具,由於目前各個語言正則的引擎各有取捨,所以線上工具的檢測結果不一定和程式碼檢測結果相同(基本上沒太大出入),但用於理解正則,還是很有用的。
簡單案例:匹配5個連續的數字
正規表示式為[0-9]{5}
,先用開源中國的線上測試工具測試一下。待匹配的字串為“自由12345飛翔
”
JAVA檢測程式碼如下
/**
* 檢測簡單方法
* @param target //待查詢匹配的字串
* @param regex //匹配規則的正規表示式
*/
public static void simpletest(String target,String regex) {
Pattern p = Pattern.compile(regex);//java.util.regex.Pattern;
Matcher matcher = p.matcher(target);//java.util.regex.Pattern;
while (matcher.find()) {
System.out.println(matcher.group(0));
}
}
simpletest("自由12345飛翔", "[0-9]{5}");
//執行結果如下
12345
普通字元
所謂普通字元即為非元字元,上文中提到的元字元列表,即不是上面列表中的字元,就視為普通字元,普通字元為原樣匹配
案例1,普通字元
simpletest("自123由飛12333翔", "123");
//執行結果如下
123
123
如上案例中,會去目標字串"自123由飛12333翔
"中查詢123
,由於123為普通字元,沒有特殊含義,此時原樣匹配,所以匹配到"自123由飛12333翔
"中兩組123
案例2,元字元
simpletest("自[]由飛翔", "[");
//執行結果如下
直接報錯 Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class
如果要讓元字元原樣匹配,則需要用\
轉義元字元,JAVA中\\
才表示普通字串的\
,所以為\\[
simpletest("自[]由飛翔", "\\[");
//執行結果如下
[
如上,通過轉義,匹配到 “自[
]由飛翔”中的元字元 [
而線上工具可以直接將字元讀入,所以不用\\
,\[
就表示匹配字元[
,如下
案例3,普通字元&
simpletest("自&&由飛翔", "&&");
//執行結果如下
&&
simpletest("自&由&飛翔", "&");
//執行結果如下
&
&
案例3可以看出,雖然&&
有特殊含義,但單獨用時,不用轉義,和普通字元
完全相同
字元類
字元類(character class)
,這裡這個詞語是個專用名詞,在JAVA API 中的Pattern類中我們可以看到字元類的一個列表。一個[]
中的規則叫一個字元類,一個字元類僅匹配一個字元(一個位置)
- [abc] a、b 或 c(簡單類)
- [^abc] 任何字元,除了 a、b 或 c(否定)
- [a-zA-Z] a 到 z 或 A 到 Z,兩頭的字母包括在內(範圍) 數字範圍也能類似表示如[0-9]代表0到9中任意一個數字
- [a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](並集) **等同於[a-d|[m-p]] 等同與 [[a-d]|[m-p]] **
- [a-z&&[def]] d、e 或 f(交集)
- [a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](減去) 差集
- [a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](減去)差集
案例
simpletest("abcd", "[abc]");
//執行結果如下
a
b
c
simpletest("abcd", "[^abc]");
//執行結果如下
d
simpletest("abcd", "[a-zA-Z]");
//執行結果如下
a
b
c
simpletest("an", "[a-d[m-p]]");
//執行結果如下
a
n
simpletest("abcd", "[a-z&&[def]]");
//執行結果如下
d
simpletest("abcd", "[a-z&&[^bc]]");
//執行結果如下
a
d
simpletest("an", "[a-z&&[^m-p]]");
//執行結果如下
a
現在我們清楚的看出來,一個字元類,也就是[]
及中間內容代表一個範圍,表示匹配一個字元,中括號中包含這個字元可能出現的所有情況,由於檢測工具中使用了迴圈查詢匹配,所以輸出結果會查詢到多個字元列印出來
預定義字元類
預定義字元類,是正規表示式中代表一組字元的特殊表示,由\
打頭,如下為JAVA API 中Pattern類的預定義字元類列表
.
: 任何字元(與行結束符可能匹配也可能不匹配)
\d
:數字:[0-9]
\D
:非數字: [^0-9]
\s
:空白字元:[ \t\n\x0B\f\r]
\S
:非空白字元:[^\s]
\w
:單詞字元:[a-zA-Z_0-9]
\W
:非單詞字元:[^\w]
案例
simpletest("自由12345飛翔", "\\d{5}"); //同理,\d在JAVA中需要轉義
//執行結果如下
12345
simpletest("自由12345飛翔", "\\D+"); //預定義數量詞稍後再說
//執行結果如下
自由
飛翔
simpletest("自由 \t飛翔", "\\s+");
//執行結果如下
` `//此處匹配到一個空格和一個製表符
simpletest("自由 \t飛翔", "\\S+");
//執行結果如下
自由
飛翔
simpletest("自由abc飛翔", "\\w+");
//執行結果如下
abc
simpletest("自由abc飛翔", "\\W+");
//執行結果如下
自由
飛翔
數量詞
預設數量詞
正則匹配中字元都有要匹配的數量,如果沒有加數量詞,預設數量為1,匹配一個的意思
案例1
simpletest("自由12345飛翔", "\\D");
//執行結果如下
自
由
飛
翔
simpletest("自由12345飛翔", "\\D+");
//執行結果如下
自由
飛翔
如上案例中\\D
代表匹配查詢非數字字元,預設數量詞為1,所以查詢到一個非數字字元後直接列印後,便進入下次查詢,結果如上第一段程式碼
如上案例中\\D+
中引入+
號預定義量詞,代表匹配大於等於1次的連續非數字字元,所以匹配到自由後進入下一次查詢
自定義量詞
使用者希望匹配幾次,就給定匹配次數,我這裡姑且叫它自定義量詞吧。由大括號,上下限數量組成,上限數量可以缺少
{n}
:恰好n個{n,}
:大於等於n個{n,m}
:大於等於n個,小於等於m個
注意:並沒有{,m}這種寫法
案例
simpletest("自由12345飛翔", "\\d{2}");
//執行結果如下
12
simpletest("自由12345飛翔", "\\d{2,}");
//執行結果如下
12345
simpletest("自由12345飛翔", "\\d{2,4}");
//執行結果如下
1234
simpletest("自由12345飛翔", "\\d{,7}");
//執行結果如下
報錯 Exception in thread "main" java.util.regex.PatternSyntaxException: Illegal repetition near index 1
從上面案例中我們已經看出,量詞只形容最近字元的數量
,大括號中可以指定具體字元的具體數量或者範圍。
預定義量詞
預定義量詞是正則中用?
,+
,*
三個符號表示特定意思的量詞
?
:一次或者零次+
:一次或者多次*
:零次或者零次以上
案例1
simpletest("自由12345飛翔", "\\d?");
//執行結果如下
//空行
//空行
1
2
3
4
5
//空行
//空行
這裡要特別解釋一下兩個空行的產生,正則引擎去自由12345飛翔
查詢\\d?
時,逐個字元從左到右查詢,由於?
表示一個或者零個,所以第一個字元匹配成功得到0個數字,也就是一個空字元,所以列印出來,而後面匹配到1個數字“1”列印出來,在匹配到1個數字“2”列印出來、、
案例2
simpletest("自由12345飛翔", "\\d+");
//執行結果如下
12345
這裡匹配至少一個數字,直接匹配到5個數字“12345”輸出
案例3
simpletest("自由12345飛翔", "\\d*");
//執行結果如下
//空行
//空行
12345
//空行
//空行
這裡出現空行的原因和案例1
中相同,因為\d*代表0次或者0次以上
邊界識別符號
如下為JAVA API 中Pattern類的邊界識別符號列表
^
:行的開頭
$
:行的結尾
\b
:單詞邊界
\B
:非單詞邊界
\A
:輸入的開頭
\G
:上一個匹配的結尾
\Z
:輸入的結尾,僅用於最後的結束符(如果有的話)
\z
:輸入的結尾
目前並沒完全弄明白所有邊界識別符號的用法,抱歉,僅演示幾個。
案例1
simpletest("自由12345飛翔", "^\\d+");
simpletest("12345飛翔", "^\\d+");
//執行結果如下
12345
^\\d+
查詢行開頭緊跟1次或多次數字,顯然自由12345飛翔
匹配失敗,因為12345
並非行首,而12345飛翔
匹配成功得到12345
案例2
simpletest("自由12345飛翔", "^\\d+$");
simpletest("12345飛翔", "^\\d+$");
simpletest("自由12345", "^\\d+$");
simpletest("12345", "^\\d+$");
//執行結果如下
12345
當\\d+
前面加上行首邊界,後面加上行尾邊界後,該正則只能匹配到一串純數字,且數量滿足+
量詞
案列3
simpletest("自由12345飛翔", "\\b\\d+");
//執行結果如下
//啥也沒有
simpletest("自由 12345飛翔", "\\b\\d+");
//執行結果如下
12345
simpletest("自由12345 飛翔", "\\d+\\b");
//執行結果如下
12345
simpletest("12345", "\\d+\\b");
//執行結果如下
12345
simpletest("12345", "\\b\\d+");
//執行結果如下
12345
從案例3中我們可以看出所謂的\b
單詞邊界就是指空格或者行首行位(或許還有其他,反正匹配到一個連續的詞的結束或者開始位置)
案例4
simpletest("自由12345飛翔", "\\B\\d+");
//執行結果如下
12345
simpletest("自由 12345飛翔", "\\B\\d+");
//執行結果如下
2345
\B
為非單詞邊界,和\b恰好相反,但案例4
中效果卻和案例3
中不是相反,不能匹配到12345
,因為前面有空格,但2345
前面是1
,是非單詞邊界,所以匹配成功
正則組
至此前面,基本上把正規表示式簡單運用講完,現在我們來引入一個詞正則組
,正則組是用()
把一組字元當做一個整體,可以通過方法將這個組匹配到的字元單獨取出,同樣可以通過下標引用之前匹配到的該組的字元
簡單應用
案例
@Test
public void grouptest( ){
String regex="\\d(\\d+)(\\D+)";
String target="520LiLing";
Pattern p = Pattern.compile(regex);
Matcher matcher = p.matcher(target);
while (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
}
}
//執行結果如下
520LiLing
20
LiLing
Exception in thread "main" java.lang.IndexOutOfBoundsException: No group 3
從簡單案例中可以看出()
中匹配到的數字可以用matcher.group(1)方法取出,1代表第一組,案例中第一組為(\d+),第二組為(\D+),第三組沒找到,報錯。而matcher.group(1)代表整個正規表示式匹配到的內容。
複雜組序
JAVA API 中Pattern類中有關於正則組順序的介紹,當遇到複雜的正則組時,怎麼來確定組的序號。
((A)(B(C)))
表示一個正規表示式,A,B,C分別代表隨意一個表示式
- group(1):
((A)(B(C)))
- group(2):
(A)
- group(3):
(B(C))
- group(4):
(C)
從上面的列表說明不難總結出一個規律,將正規表示式從左到右讀過來,依次遇到()
中的左括號(
依次編號,先遇到哪一組的左括號先編號
案例
@Test
public void grouptest( ){
String regex="((\\d)(\\d+(\\D+)))";
String target="520LiLing";
Pattern p = Pattern.compile(regex);
Matcher matcher = p.matcher(target);
while (matcher.find()) {
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
System.out.println(matcher.group(4));
}
}
//執行結果如下
520LiLing
5
20LiLing
LiLing
案例和上文說明完全一致,1組為((\\d)(\\d+(\\D+)))
,2組為(\\d)
,3組為(\\d+(\\D+))
,4組為(\\D+)
捕獲組
前面已經講過關於正則組的編號,以及引用,但這樣的作用似乎還不夠強大。捕獲組,就是將之前的正則組通過\組序號
捕獲,如\1
(任然需要轉義\\1
),再次利用。(解釋起來太費勁,看案例吧)
@Test
public void grouptest( ){
String regex="(\\d+).+\\1";
String target="520Li320Ling";
Pattern p = Pattern.compile(regex);
Matcher matcher = p.matcher(target);
while (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
//執行結果如下
20Li320
20
上面案例中,正規表示式(\\d+).+\\1
的意思就是先查詢到數字標記為組1
,然後跟著任意字元,跟著\\1
表示捕獲和組1
一模一樣的內容。