1. 程式人生 > >正則表示式(以Java語言為例)

正則表示式(以Java語言為例)

正則表示式的概念:
用來匹配和處理文字的字串。人們常用模式(pattern)來表示實際的正則表示式。正則表示式是由正則表示式語言建立的。正則表示式語言是內置於其他語言或軟體產品裡的”迷你“語言,但它並不是一種完備的程式設計語言。不同的程式語言或應用程式裡,正則表示式的語法和功能會有所不同。

正則表示式的用途:
(1)搜尋(匹配):在一個字串中搜索出一個或多個與正則表示式相匹配的子字串。搜尋又分為匹配和子字串搜尋。匹配是對使用者所提供的整個字串進行判斷,看其是否匹配正則表示式,比如電子郵件地址的匹配。子字串搜尋是“搜尋”的普遍含義,指的是將與正則表示式相匹配的所有子字串找出來,比如將一段英文文字中的所有單詞給找出來。
(2)替換(匹配並替換):將一個字串中與正則表示式相匹配的子字串找出來並替換成另一些子字串,比如將一個字串中的所有的cos替換成sin。

匹配單個字元
(1)匹配純文字
正則表示式裡可以包含純文字(甚至可以只包含純文字)。
匹配純文字時可能會有多個匹配結果,絕大多數的正則表示式的實現都提供了一種能夠將所有的匹配結果都找出來的機制(通常是返回一個數組)。
Java中的正則表示式是區分大小寫的。

文字:
Hello,my name is Ben.
正則表示式:
Ben
結果:
Hello,my name is Ben.

(2)匹配任意字元
.字元可以匹配任意一個單個的字元(字元、字母、數字、.字元本身,與行結束符可能匹配也可能不匹配)。

(3)匹配特殊字元
這裡的特殊字元指的是正則表示式裡的特殊字元,注意與Java語言裡的轉義字元相區分。
正則表示式裡的特殊字元指的是該字元在正則表示式中有其特殊的含義,不是簡單的純文字。
如果要匹配特殊字元本身,就需要在在特殊字元的開頭就加上\。不過在Java裡到底要加多少個\,這個問題是比較令人頭疼的,具體解析詳見[關於JAVA正則表示式裡的](

http://zhidao.baidu.com/question/1637942975153285780.html)。

正則表示式中需要轉義的特殊字元:

$    匹配輸入字串的結尾位置。
( )  標記一個子表示式的開始和結束位置。
*    匹配前面的子表示式零次或多次。
+    匹配前面的子表示式一次或多次。
.    匹配除換行符 \n之外的任何單字元。
[ ]  標記一箇中括號表示式的開始。
?    匹配前面的子表示式零次或一次,或指明一個非貪婪限定符。
\    轉義下一個字元標記。
^    匹配輸入字串的開始位置。
{ }  標記限定符表示式的開始。
|    指明兩項之間的一個選擇。

Java語言中轉義字元:

1.八進位制轉義序列:
\0n     帶有八進位制值 0 的字元 n (0 <= n <= 7) 
\0nn    帶有八進位制值 0 的字元 nn (0 <= n <= 7) 
\0mnn   帶有八進位制值 0 的字元 mnn(0 <= m <= 3、0 <= n <= 7) 
2.十六進位制轉義序列:
\xhh    帶有十六進位制值 0x 的字元 hh 
\uhhhh  帶有十六進位制值 0x 的字元 hhhh 
3. 特殊字元:
\"   雙引號 
\'   單引號 
\\   反斜線 
\a   報警 (bell) 符
\e   轉義符
\cx 對應於x的控制符 
4. 控制字元: 
\t   製表符 ('\u0009') 
\n   換行符 ('\u000A') 
\r   回車符 ('\u000D') 
\f   換頁符 ('\u000C') 

匹配一組字元
(1)匹配多個字元中的某一個
使用元字元[]來定義一個字元集合,這兩個元字元之間的所有字元都是該集合的組成部分,字元集合匹配結果是能夠與該集合裡的任意一個字元相匹配的字元。
文字:
The phrase “regular expression” is often abbreviates as RegEx or regex.
正則表示式:
[Rr]eg[Ee]x
結果:
The phrase “regular expression” is often abbreviates as RegEx or regex.

(2)利用字元集合區間
為了簡化字元區間的定義,正則表示式提供了一個特殊的元字元-,字元區間可以用-(連字元)來定義。
字元區間的首、尾字元可以是ASCII字元表裡的任意字元,但在實際的應用中,常用的還是數字字元區間和 字母字元區間。
在定義一個字元區間的時候一定要避免讓這個字元區間的尾字元小於它的首字元。
在同一個字元集合裡可以有多個字元區間。
-字元是一個特殊的元字元,作為元字元它只能是用在[]之間。在字元集合以外的地方,-只是一個普通的字元,只能與-本身相匹配,因此,在正則表示式裡,-字元不需要被轉移。

(3)取非匹配
用元字元^來對一個字元區間進行取非匹配。表示出了那個字元集合裡的字元,其他字元都可以匹配。
^的效果將作用於給定字元集合裡的所有字元和字元區間,而不是僅限於緊跟在^字元後的那一個字元或字元區間。

使用元字元
元字元大致可分為兩種,一種是用來匹配文字的(比如.),另一種是正則表示式的語法所要求的(比如[和])。
某些元字元之間的差異:.和[是元字元,但前提是你沒有對它進行轉義;t和n也是元字元,但前提是你對它進行了轉義。
(1)對特殊字元進行轉義
任何一個元字元都可以通過給它加上一個\字元作為字首的辦法來轉義。

(2)匹配空白字元
\r\n是Windows作業系統所使用的文字行結束標籤;
\n是Unix和Linux作業系統所使用的文字行結束標籤。
空白元字元:

[\b]  回退(並刪除)一個字元(Backspace鍵)
\f    換頁符
\n    換行符
\r    回車符
\t    製表符(Tab鍵)
\v    垂直製表符(\x0B)

(3)匹配特定的字元類別

\d   任何一個數字字元(等價於[0-9]\D   任何一個非數字字元(等價於[^0-9]\w   任何一個數字、字母、下劃線字元(等價於[a-zA-Z0-9_]\W   任何一個非(數字、字母、下劃線字元)(等價於[^a-zA-Z0-9_]\s   任何一個空白字元(等價於[\f\n\r\t\v]\S   任何一個非空白字元(等價於[^\f\n\r\t\v]

(4)在Java中使用POSIX字元類

\p{Lower}    小寫字母字元:[a-z] 
\p{Upper}    大寫字母字元:[A-Z] 
\p{ASCII}    所有 ASCII:[\x00-\x7F] 
\p{Alpha}    字母字元:[\p{Lower}\p{Upper}] 
\p{Digit}    十進位制數字:[0-9] 
\p{Alnum}    字母數字字元:[\p{Alpha}\p{Digit}] 
\p{Punct}    標點符號:!"#$%&'()*+,-./:;<=>[email protected][\]^_`{|}~ 
\p{Graph}    可見字元:[\p{Alnum}\p{Punct}] 
\p{Print}    可列印字元:[\p{Graph}\x20] 
\p{Blank}    空格或製表符:[ \t] 
\p{Cntrl}    控制字元:[\x00-\x1F\x7F] 
\p{XDigit}   十六進位制數字:[0-9a-fA-F] 
\p{Space}    空白字元:[ \t\n\x0B\f\r] 

重複匹配
(1)有多少個匹配
再給一個字元集合加上+字尾的時候,必須把+放在這個字元集合的外面。比如說:[0-9]+是正確的,[0-9+]則不是。[0-9+]其實也是合法的正則表示式,但它匹配的不是一個或多個數字,它定義了一個由數字0到9和+構成的字元集合。*和?依次類推。
+是一個元字元。如果需要匹配+本身,就必須使用它的轉義序列+。*和?依次類推。
當在字元集合裡使用的時候,像.和+這樣的元字元將被解釋成普通字元,不需要被轉義,但轉義了也沒有壞處。即[.]的使用效果與[.]是一樣的。
1.匹配一個或多個字元 +
2.匹配零個或多個字元 *
3.匹配零個或一個字元 ?

(2)匹配的重複次數
1.為重複匹配的次數設定一個精確的值,例如[0-9]{3}。
2.為重複匹配的次數設定一個區間,例如[0-9]{3,5},注意是逗號,不是連線符。
3.匹配”至少重複多少次“,例如[0-9]{3,},注意別漏了逗號。

(3)防止過度匹配
文字:
This offer is not available to customers living in < B>AK and < B>HI< /B>
正則表示式:
<[Bb]>.*< /[Bb]>
結果:
This offer is not available to customers living in < B>AK< /B> and < B>HI< /B>
解釋:
因為*和+都是所謂的“貪婪型”元字元,它們在進行匹配時的行為模式是多多益善而不是適可而止。它們會盡可能地從一段文字的開頭一直匹配到這段文字的末尾,而不是從這段文字的開頭到碰到第一個匹配時為止。
與貪婪模式相反的額是懶惰模式,只要給“貪婪型”字元加上一個?字尾即可。

“貪婪型”元字元       “懶惰型”元字元
     *                  *?
     +                  +?
     {n,}               {n,}?

文字:
This offer is not available to customers living in < B>AK and < B>HI< /B>
正則表示式:
<[Bb]>.*?< /[Bb]>
結果:
This offer is not available to customers living in < B>AK< /B> and < B>HI< /B>

位置匹配
如果只需要對某段文本里的特定位置進行匹配,通過位置匹配可以解決這個問題,也就是使用邊界限定符。在正則表示式裡用一些特殊的元字元來表明想讓匹配操作在什麼位置(可以理解成邊界)發生。邊界分為單詞邊界、非單詞邊界、行邊界、字串邊界。

^   字元的開頭(在(?m)限定下為行的開頭) 
$   字串的結尾 (在(?m)限定下為行的結尾) 
\b  單詞邊界(包括開頭和結尾)
\B  非單詞邊界(包括開頭和結尾)
\A  字串的開頭 
\Z  字串的結尾

(1)單詞邊界
由限定符\b指定的單詞邊界 (b 指 boundary邊界), \b用來匹配一個單詞的開始或結尾。
\b 匹配的是這樣一個位置: 這個位置位於一個能夠用來構成單詞的字元(字母、數字和下劃線) 和一個不能用來構成單詞的字元 之間。
\b只匹配一個位置,不匹配任何字元。用\b cat \b匹配到的字串是長度為3的字串(c、a、t),不是長度為5的字串。
如果要匹配一個完整的單詞,必須在要匹配的文字的前後都加上\b限定符。

文字:
The cat scattered his food all over the room
正則表示式:
\bcat\b
結果:
The cat scattered his food all over the room

(2)非單詞邊界
如果不匹配一個單詞邊界使用\B

文字:
Please enter the [email protected]@@digit id as it appears on your color @@@ coded pass-key
正則表示式:
\[email protected]@@\B
結果:
Please enter the [email protected]@@digit id as it appears on your color @@@ coded pass-key

(3)字串邊界
用來定義字串邊界的元字元有: 一個用來定義字串開頭的 ^, 另一個是用來定義字串結尾的 $.
^ 是幾個有著多種用途的元字元之一。只有當它出現在一個字元集合裡(放在[和]之間) 並緊路在左方括號 [ 的後面時,它才能發揮 “求非” 作用。如果是在一個字元集合的外面並位於一個模式的開頭, ^ 將匹配字串的開頭.
用來定義字串邊界的元字元還有: 一個用來定義字串開頭的 \A, 另一個是用來定義字串結尾的 \Z.

下面的例子只是示範了^元字元的用法,並沒有示範元字元的用法類似。

文字:
<?xml  version="1.0"  encoding="UTF-8" ?>
<wsdl:definitions  targetNamespace=http://tips.cf>
正則表示式: 
^\s*<\?xml.*?\?>
結果:
文字的第一行將會被匹配
解釋:
在這裡 ^\s* 將匹配一個字串的開頭位置和隨後的零或多個空白字元, 注意還用到了懶惰型元字元。

(4)行邊界
正則表示式支援使用一些特殊的元字元去改變另外一些元字元行為的做法,用來啟用分行匹配模式(multiline mode) 的 (?m) 記號就是一個能夠改變其他元字元行為的元字元序列。
預設情況下,正則表示式 ^ 和 (?m)使不僅匹配正常的字串結尾,還匹配行分隔符後面的結束位置。
但是\A和\Z不受分行匹配模式的影響。
在使用時, (?m) 必須出現在整個模式的最前面。

子表示式
子表示式是一個更強大的表示式的一部分,把一個表示式劃分為一系列的字表達式的木的時為了把那些子表示式當作一個獨立的元素來使用。子表示式必須用()括起來。
子表示式的用途:對子表示式的重複次數做出精確的設定和控制、對|操作符的OR條件做出準確的定義、回溯引用。
(和)都是元字元。如果需要匹配(和)本身,就必須使用它的轉義序列(和)。
子表示式還允許巢狀使用。

(1)對子表示式的重複次數做出精確的設定和控制
文字:
ping www.baidu.com [192.168.1.200]
正則表示式:
(\d{1,3}.){3}\d{1,3}
結果:
ping www.baidu.com [192.168.1.200]

(2)正則表示式的或操作符|和子表示式的巢狀使用
文字:
ping www.baidu.com [192.168.1.200]
正則表示式:
(((\d{1,2})|(1\d{2})|(24\d)|(25[0-5])).){3}((\d{1,2})|(1\d{2})|(24\d)|(25[0-5]))
結果:
ping www.baidu.com [192.168.1.200]

(3)回溯引用
將在下章詳細介紹。

回溯引用(backreference)
(1)回溯引用的問題引入

一個在HTML頁面中匹配標題標籤(H1—H6)的問題:
文字:
<body>
<h1>Welcome to my page</H1>
Content is divided into twosections:<br>
<h2>Introduction</h2>
Information about me.
<H2>Hobby</H2>
Information about my hobby.
<h2>This is invalid HTML</h3>
</body>

正則表示式:
<[hH][1-6]>.*?</[hH][1-6]>

結果:
<body><h1>Welcome to my page</H1>】
Content is divided into twosections:<br><h2>Introduction</h2>】
Information about me.
【<H2>Hobby</H2>】
Information about my hobby.
【<h2>This is invalid HTML</h3></body>

分析:
模式<[hH][1-6]>匹配任何一級標題的開始標籤,而且不區分大小寫,在這個例子中它匹配到了<h1><h2></[hH][1-6]>匹配到了</h1></h2></h3>;這裡使用了懶惰型元字元來匹配標籤中的文字,否則會匹配到從第一個開始標籤到最後一下結束標籤之間的內容。但是從結果可以看出,有一個無效的標籤也匹配上了,即<h2></h3>,它們根本不能配對。要解決這個問題,就需要使用到回溯引用。

(2)回溯引用匹配
回溯引用是指模式的後半部分引用在前半部分中定義的子表示式。
回溯引用只能用來引用模式裡的字表達式(用(和)括起來的正則表示式片段)。
同一個子表示式可以被引用多次。
\1代表的是模式裡的第一個子表示式,\2代表的時模式裡的第二個子表示式,依次類推。可以把回溯引用理解成是變數的引用。Java中第0個匹配\0可以用來代表整個正則表示式。
子表示式在回溯引用中也稱為捕獲組。

捕獲組索引的計算方式:
在模式 ((A)(B(C))) 中,存在四個這樣的捕獲組:
\1 ((A)(B(C)))
\2 (A)
\3 (B(C))
\4 (C)
當然\0代表((A)(B(C)))

文字:
<body>
<h1>Welcome to my page</H1>
Content is divided into twosections:<br>
<h2>Introduction</h2>
Information about me.
<H2>Hobby</H2>
Information about my hobby.
<h2>This is invalid HTML</h3>
</body>

正則表示式:
<[hH]([1-6])>.*?</[hH]\1>

結果:
<body><h1>Welcome to my page</H1>】
Content is divided into twosections:<br><h2>Introduction</h2>】
Information about me.
【<H2>Hobby</H2>】
Information about my hobby.
<h2>This is invalid HTML</h3>

分析:
首先匹配開始標題標籤的模式<[hH]([1-6])>,使用括號把[1-6]做為子表示式,而匹配結束標題標籤模式為</[hH]\1>,其中\1表示引用第一個子表示式,即([1-6]),如果([1-6])匹配到的是1,那\1也匹配到1,如果匹配到2,那\1也匹配到2,所以最後一個無效的標題標籤就不會被匹配到了。

(3)回溯引用在替換中的操作(後向分組引用(1,2…))
Java正則表示式支援替換時用 1,2 那樣的後向分組引用。

String s = "abc def".replaceAll("(\\w+)\\s+(\\w+)", "$2 $1"); 
執行完上語句後,s 就是 "def abc",replaceFirst 也可以用 $1, $2 的替換。

前後查詢(lookaround)
前後查詢中的前、後是指模式與被查詢文字的相對位置而言的,左為前,右為後。即向前查詢為:xxx(?=xxx),而向後查詢為(?<=xxx)xxx。
前後查詢模式一定要放在()裡括起來。
有了前後查詢,就可以對最終的匹配結果包含哪些內容做出精確的控制。前後查詢操作使我們可以利用子表示式來指定文字匹配操作發生的位置,並收到只匹配不消費的效果。
(1)前後查詢的問題引入

在HTML頁面中,匹配出一對標籤之間的文字,如匹配出頁面的標籤,即<title></title>之間的文字:
文字:
<head><TITLE>welcome to my page</title></head>

正則表示式:
<[Tt][Ii][Tt][Ll][Ee]>.*?</ [Tt][Ii][Tt][Ll][Ee]>

結果:
<head><TITLE>welcome to my page</title></head>

分析:
<[Tt][Ii][Tt][Ll][Ee]>表示不區分大小寫,這個模式匹配到了title標籤以及它們之間的文字,但是並不完美,因為我們只想要title標籤之間的文字,而不包括標籤本身。解決這個問題我們就需要用到前後查詢。

(2)向前查詢(lookahead)

向前查詢指定了一個必須匹配但不在結果中返回的模式。向前查詢實際上就是一個子表示式,它以?=開頭,需要匹配的文字跟在=的後面。

看一個匹配出一個URL地址中協議部分的例子:
文字:
http://blog.csdn.net/mhmyqn

正則表示式:
.+(?=:)

結果:
【http】://blog.csdn.net/mhmyqn

分析:
URL地址中協議部分是在:之前的部分,模式.+匹配任意文字,子表示式(?=:)匹配:,但是被匹配到的:並沒有出現在結果中。我們使用?=向正則表示式引擎表明,只要找到:就行了,但不包括在最終的返回結果裡。這裡如果不使用向前匹配(?=:),而是直接使用(:),那麼匹配結果就會是http:了,它包括了:,並不是我們想要的。

(3)向後查詢(lookbehind)

向後查詢操作符是?<=。但是並不是所有的正則表示式實現都支援向後查詢,javascript就不支援,java語言支援向後查詢。

比如要查詢文本當中的價格(以$開頭,後面跟數字),結果不包含貨幣符號:
文字:
category1:$136.25,category2:$28,category3:$88.60

正則表示式:
(?<=\$)\d+(\.\d+)?

結果:
category1:$【136.25】,category2:$【28】,category3:$【88.60】

分析:(?<=\$)模式匹配$\d+(\.\d+)?模式匹配整數或小數。從結果可以看出,結果不沒有包括貨幣符號,只匹配出了價格。如果不使用向後查詢,情況會是什麼樣呢?使用模式$\d+(\.\d+)?,這樣會把$包含在結果中。使用模式\d+(\.\d+)?,又會把categery1(23)中的數字也匹配出來,都不是我們想要的。

注意:
向前查詢模式的長度是可變的,它們可以包含.、*、+之類的元字元;而向後查詢模式只能是固定長度,不能包含.、*、+之類的元字元。

(4)把向前查詢和向後查詢結合起來

把向前查詢和向後查詢結合起來使用,即可解決前面HTML標籤之間的文字的問題:
文字:
<head><TITLE>welcome to my page</