posts - 195, comments - 34, trackbacks - 0, articles - 1

          再見regex

          Posted on 2009-06-17 20:44 小強摩羯座 閱讀(269) 評論(0)  編輯  收藏 所屬分類: Java

          正則表達式規則


          String.matches() 這個方法主要是返回是否匹配指定的字符串,如果匹配則為true,否則為false;

          如:/**
              * 判斷字符創是否是一個有效的日期
              *
              * @param theStr
              * @return true 是,false否
              */
          public static boolean isDate(String theStr) {
              return theStr.matches("\\d{4}\\-\\d{1,2}\\-\\d{1,2}");
          }

          這個方法的參數為正則表達式,關于正則表達式的用法如下:

              正則表達式(regular expression)描述了一種字符串匹配的模式,可以用來:(1)檢查一個串中是否含有符合某個規則的子串,并且可以得到這個子串;(2)根據匹配規則對字符串進行靈活的替換操作。

                正則表達式學習起來其實是很簡單的,不多的幾個較為抽象的概念也很容易理解。之所以很多人感覺正則表達式比較復雜,一方面是因為大多數的文檔沒有做到由淺入深地講解,概念上沒有注意先后順序,給讀者的理解帶來困難;另一方面,各種引擎自帶的文檔一般都要介紹它特有的功能,然而這部分特有的功能并不是我們首先要理解的。

                文章中的每一個舉例,都可以點擊進入到測試頁面進行測試。閑話少說,開始。  

          1. 正則表達式規則

          1.1 普通字符

                字母、數字、漢字、下劃線、以及后邊章節中沒有特殊定義的標點符號,都是"普通字符"。表達式中的普通字符,在匹配一個字符串的時候,匹配與之相同的一個字符。

              舉例1:表達式 "c",在匹配字符串 "abcde" 時,匹配結果是:成功;匹配到的內容是:"c";匹配到的位置是:開始于2,結束于3。(注:下標從0開始還是從1開始,因當前編程語言的不同而可能不同)

              舉例2:表達式 "bcd",在匹配字符串 "abcde" 時,匹配結果是:成功;匹配到的內容是:"bcd";匹配到的位置是:開始于1,結束于4。


          1.2 簡單的轉義字符

                一些不便書寫的字符,采用在前面加 "\" 的方法。這些字符其實我們都已經熟知了。

           

          表達式

          可匹配

          \r, \n

          代表回車和換行符

          \t

          制表符

          \\

          代表 "\" 本身

                還有其他一些在后邊章節中有特殊用處的標點符號,在前面加 "\" 后,就代表該符號本身。比如:^, $ 都有特殊意義,如果要想匹配字符串中 "^" 和 "$" 字符,則表達式就需要寫成 "\^" 和 "\$"。

           

          表達式

          可匹配

          \^

          匹配 ^ 符號本身

          \$

          匹配 $ 符號本身

          \.

          匹配小數點(.)本身

                這些轉義字符的匹配方法與 "普通字符" 是類似的。也是匹配與之相同的一個字符。

              舉例1:表達式 "\$d",在匹配字符串 "abc$de" 時,匹配結果是:成功;匹配到的內容是:"$d";匹配到的位置是:開始于3,結束于5。


          1.3 能夠與 '多種字符' 匹配的表達式

                正則表達式中的一些表示方法,可以匹配 '多種字符' 其中的任意一個字符。比如,表達式 "\d" 可以匹配任意一個數字。雖然可以匹配其中任意字符,但是只能是一個,不是多個。這就好比玩撲克牌時候,大小王可以代替任意一張牌,但是只能代替一張牌。

           

          表達式

          可匹配

          \d

          任意一個數字,0~9 中的任意一個

          \w

          任意一個字母或數字或下劃線,也就是 A~Z,a~z,0~9,_ 中任意一個

          \s

          包括空格、制表符、換頁符等空白字符的其中任意一個

          .

          小數點可以匹配除了換行符(\n)以外的任意一個字符

              舉例1:表達式 "\d\d",在匹配 "abc123" 時,匹配的結果是:成功;匹配到的內容是:"12";匹配到的位置是:開始于3,結束于5。

              舉例2:表達式 "a.\d",在匹配 "aaa100" 時,匹配的結果是:成功;匹配到的內容是:"aa1";匹配到的位置是:開始于1,結束于4。


          1.4 自定義能夠匹配 '多種字符' 的表達式

                使用方括號 [ ] 包含一系列字符,能夠匹配其中任意一個字符。用 [^ ] 包含一系列字符,則能夠匹配其中字符之外的任意一個字符。同樣的道理,雖然可以匹配其中任意一個,但是只能是一個,不是多個。

           

          表達式

          可匹配

          [ab5@]

          匹配 "a" 或 "b" 或 "5" 或 "@"

          [^abc]

          匹配 "a","b","c" 之外的任意一個字符

          [f-k]

          匹配 "f"~"k" 之間的任意一個字母

          [^A-F0-3]

          匹配 "A"~"F","0"~"3" 之外的任意一個字符

              舉例1:表達式 "[bcd][bcd]" 匹配 "abc123" 時,匹配的結果是:成功;匹配到的內容是:"bc";匹配到的位置是:開始于1,結束于3。

              舉例2:表達式 "[^abc]" 匹配 "abc123" 時,匹配的結果是:成功;匹配到的內容是:"1";匹配到的位置是:開始于3,結束于4。


          1.5 修飾匹配次數的特殊符號

                前面章節中講到的表達式,無論是只能匹配一種字符的表達式,還是可以匹配多種字符其中任意一個的表達式,都只能匹配一次。如果使用表達式再加上修飾匹配次數的特殊符號,那么不用重復書寫表達式就可以重復匹配。

                使用方法是:"次數修飾"放在"被修飾的表達式"后邊。比如:"[bcd][bcd]" 可以寫成 "[bcd]{2}"。

           

          表達式

          作用

          {n}

          表達式重復n次,比如:"\w{2}" 相當于 "\w\w""a{5}" 相當于 "aaaaa"

          {m,n}

          表達式至少重復m次,最多重復n次,比如:"ba{1,3}"可以匹配 "ba"或"baa"或"baaa"

          {m,}

          表達式至少重復m次,比如:"\w\d{2,}"可以匹配 "a12","_456","M12344"...

          ?

          匹配表達式0次或者1次,相當于 {0,1},比如:"a[cd]?"可以匹配 "a","ac","ad"

          +

          表達式至少出現1次,相當于 {1,},比如:"a+b"可以匹配 "ab","aab","aaab"...

          *

          表達式不出現或出現任意次,相當于 {0,},比如:"\^*b"可以匹配 "b","^^^b"...

              舉例1:表達式 "\d+\.?\d*" 在匹配 "It costs $12.5" 時,匹配的結果是:成功;匹配到的內容是:"12.5";匹配到的位置是:開始于10,結束于14。

              舉例2:表達式 "go{2,8}gle" 在匹配 "Ads by goooooogle" 時,匹配的結果是:成功;匹配到的內容是:"goooooogle";匹配到的位置是:開始于7,結束于17。


          1.6 其他一些代表抽象意義的特殊符號

                一些符號在表達式中代表抽象的特殊意義:

           

          表達式

          作用

          ^

          與字符串開始的地方匹配,不匹配任何字符

          $

          與字符串結束的地方匹配,不匹配任何字符

          \b

          匹配一個單詞邊界,也就是單詞和空格之間的位置,不匹配任何字符

                進一步的文字說明仍然比較抽象,因此,舉例幫助大家理解。

              舉例1:表達式 "^aaa" 在匹配 "xxx aaa xxx" 時,匹配結果是:失敗。因為 "^" 要求與字符串開始的地方匹配,因此,只有當 "aaa" 位于字符串的開頭的時候,"^aaa" 才能匹配,比如:"aaa xxx xxx"

              舉例2:表達式 "aaa$" 在匹配 "xxx aaa xxx" 時,匹配結果是:失敗。因為 "$" 要求與字符串結束的地方匹配,因此,只有當 "aaa" 位于字符串的結尾的時候,"aaa$" 才能匹配,比如:"xxx xxx aaa"

              舉例3:表達式 ".\b." 在匹配 "@@@abc" 時,匹配結果是:成功;匹配到的內容是:"@a";匹配到的位置是:開始于2,結束于4。
                進一步說明:"\b" 與 "^" 和 "$" 類似,本身不匹配任何字符,但是它要求它在匹配結果中所處位置的左右兩邊,其中一邊是 "\w" 范圍,另一邊是 非"\w" 的范圍。

              舉例4:表達式 "\bend\b" 在匹配 "weekend,endfor,end" 時,匹配結果是:成功;匹配到的內容是:"end";匹配到的位置是:開始于15,結束于18。

                一些符號可以影響表達式內部的子表達式之間的關系:

           

          表達式

          作用

          |

          左右兩邊表達式之間 "或" 關系,匹配左邊或者右邊

          ( )

          (1). 在被修飾匹配次數的時候,括號中的表達式可以作為整體被修飾
          (2). 取匹配結果的時候,括號中的表達式匹配到的內容可以被單獨得到

              舉例5:表達式 "Tom|Jack" 在匹配字符串 "I'm Tom, he is Jack" 時,匹配結果是:成功;匹配到的內容是:"Tom";匹配到的位置是:開始于4,結束于7。匹配下一個時,匹配結果是:成功;匹配到的內容是:"Jack";匹配到的位置時:開始于15,結束于19。

              舉例6:表達式 "(go\s*)+" 在匹配 "Let's go go go!" 時,匹配結果是:成功;匹配到內容是:"go go go";匹配到的位置是:開始于6,結束于14。

              舉例7:表達式 "(\d+\.?\d*)" 在匹配 "$10.9,¥20.5" 時,匹配的結果是:成功;匹配到的內容是:"¥20.5";匹配到的位置是:開始于6,結束于10。單獨獲取括號范圍匹配到的內容是:"20.5"。


          2. 正則表達式中的一些高級規則

          2.1 匹配次數中的貪婪與非貪婪

                在使用修飾匹配次數的特殊符號時,有幾種表示方法可以使同一個表達式能夠匹配不同的次數,比如:"{m,n}", "{m,}", "?", "*", "+",具體匹配的次數隨被匹配的字符串而定。這種重復匹配不定次數的表達式在匹配過程中,總是盡可能多的匹配。比如,針對文本 "dxxxdxxxd",舉例如下:

           

          表達式

          匹配結果

          (d)(\w+)

          "\w+" 將匹配第一個 "d" 之后的所有字符 "xxxdxxxd"

          (d)(\w+)(d)

          "\w+" 將匹配第一個 "d" 和最后一個 "d" 之間的所有字符 "xxxdxxx"。雖然 "\w+" 也能夠匹配上最后一個 "d",但是為了使整個表達式匹配成功,"\w+" 可以 "讓出" 它本來能夠匹配的最后一個 "d"

                由此可見,"\w+" 在匹配的時候,總是盡可能多的匹配符合它規則的字符。雖然第二個舉例中,它沒有匹配最后一個 "d",但那也是為了讓整個表達式能夠匹配成功。同理,帶 "*" 和 "{m,n}" 的表達式都是盡可能地多匹配,帶 "?" 的表達式在可匹配可不匹配的時候,也是盡可能的 "要匹配"。這 種匹配原則就叫作 "貪婪" 模式 。

                非貪婪模式:

                在修飾匹配次數的特殊符號后再加上一個 "?" 號,則可以使匹配次數不定的表達式盡可能少的匹配,使可匹配可不匹配的表達式,盡可能的 "不匹配"。這種匹配原則叫作 "非貪婪" 模式,也叫作 "勉強" 模式。如果少匹配就會導致整個表達式匹配失敗的時候,與貪婪模式類似,非貪婪模式會最小限度的再匹配一些,以使整個表達式匹配成功。舉例如下,針對文本 "dxxxdxxxd" 舉例:

           

          表達式

          匹配結果

          (d)(\w+?)

          "\w+?" 將盡可能少的匹配第一個 "d" 之后的字符,結果是:"\w+?" 只匹配了一個 "x"

          (d)(\w+?)(d)

          為了讓整個表達式匹配成功,"\w+?" 不得不匹配 "xxx" 才可以讓后邊的 "d" 匹配,從而使整個表達式匹配成功。因此,結果是:"\w+?" 匹配 "xxx"

                更多的情況,舉例如下:

              舉例1:表達式 "<td>(.*)</td>" 與字符串 "<td><p>aa</p></td> <td><p>bb</p></td>" 匹配時,匹配的結果是:成功;匹配到的內容是 "<td><p>aa</p></td> <td><p>bb</p></td>" 整個字符串, 表達式中的 "</td>" 將與字符串中最后一個 "</td>" 匹配。

              舉例2:相比之下,表達式 "<td>(.*?)</td>" 匹配舉例1中同樣的字符串時,將只得到 "<td><p>aa</p></td>", 再次匹配下一個時,可以得到第二個 "<td><p>bb</p></td>"。


          2.2 反向引用 \1, \2...

                表達式在匹配時,表達式引擎會將小括號 "( )" 包含的表達式所匹配到的字符串記錄下來。在獲取匹配結果的時候,小括號包含的表達式所匹配到的字符串可以單獨獲取。這一點,在前面的舉例中,已經多次展示了。在實際應用場合中,當用某種邊界來查找,而所要獲取的內容又不包含邊界時,必須使用小括號來指定所要的范圍。比如前面的 "<td>(.*?)</td>"。

                其實,"小括號包含的表達式所匹配到的字符串" 不僅是在匹配結束后才可以使用,在匹配過程中也可以使用。表達式后邊的部分,可以引用前面 "括號內的子匹配已經匹配到的字符串"。引用方法是 "\" 加上一個數字。"\1" 引用第1對括號內匹配到的字符串,"\2" 引用第2對括號內匹配到的字符串……以此類推,如果一對括號內包含另一對括號,則外層的括號先排序號。換句話說,哪一對的左括號 "(" 在前,那這一對就先排序號。

                舉例如下:

              舉例1:表達式 "('|")(.*?)(\1)" 在匹配 " 'Hello', "World" " 時,匹配結果是:成功;匹配到的內容是:" 'Hello' "。再次匹配下一個時,可以匹配到 " "World" "。

              舉例2:表達式 "(\w)\1{4,}" 在匹配 "aa bbbb abcdefg ccccc 111121111 999999999" 時,匹配結果是:成功;匹配到的內容是 "ccccc"。再次匹配下一個時,將得到 999999999。這個表達式要求 "\w" 范圍的字符至少重復5次,注意與 "\w{5,}" 之間的區別

              舉例3:表達式 "<(\w+)\s*(\w+(=('|").*?\4)?\s*)*>.*?</\1>" 在匹配 "<td id='td1' style="bgcolor:white"></td>" 時,匹配結果是成功。如果 "<td>" 與 "</td>" 不配對,則會匹配失敗;如果改成其他配對,也可以匹配成功。

          在java語言中使用正則表達式

            首先讓我們構成一個正則表達式。為簡單起見,先構成一個正則表達式來識別下面格式的電話號碼數字:(nnn)nnn-nnnn。  
             
            第一步,創建一個pattern對象來匹配上面的子字符串。一旦程序運行后,如果需要的話,可以讓這個對象一般化。匹配上面格式的正則表達可以這樣構成: (\d{3})\s\d{3}-\d{4},其中\d單字符類型用來匹配從0到9的任何數字,另外{3}重復符號,是個簡便的記號,用來表示有3個連續的數字位,也等效于(\d\d\d)。\s也另外一個比較有用的單字符類型,用來匹配空格,比如Space鍵,tab鍵和換行符。  
             
            是不是很簡單?但是,如果把這個正則表達式的模式用在java程序中,還要做兩件事。對java的解釋器來說,在反斜線字符(\)前的字符有特殊的含義。在java中,與regex有關的包,并不都能理解和識別反斜線字符(\),盡管可以試試看。但為避免這一點,即為了讓反斜線字符(\)在模式對象中被完全地傳遞,應該用雙反斜線字符(\)。此外圓括號在正則表達中兩層含義,如果想讓它解釋為字面上意思(即圓括號),也需要在它前面用雙反斜線字符(\)。也就是像下面的一樣:  
             
            \\(\\d{3}\\)\\s\\d{3}-\\d{4}  
             
            現在介紹怎樣在java代碼中實現剛才所講的正則表達式。要記住的事,在用正則表達式的包時,在你所定義的類前需要包含該包,也就是這樣的一行:  
             
            import   java.util.regex.*;  
             
            下面的一段代碼實現的功能是,從一個文本文件逐行讀入,并逐行搜索電話號碼數字,一旦找到所匹配的,然后輸出在控制臺。  
             
            BufferedReader   in;  
             
            Pattern   pattern   =   Pattern.compile("\\(\\d{3}\\)\\s\\d{3}-\\d{4}");  
             
            in   =   new   BufferedReader(new   FileReader("phone"));  
             
            String   s;  
             
            while   ((s   =   in.readLine())   !=   null)  
             
            {  
             
            Matcher   matcher   =   pattern.matcher(s);  
             
            if   (matcher.find())  
             
            {  
             
            System.out.println(matcher.group());  
             
            }  
             
            }  
             
            in.close();  
             
            對那些熟悉用Python或Javascript來實現正則表達式的人來說,這段代碼很平常。在Python和Javascript這些語言中,或者其他的語言,這些正則表達式一旦明確地編譯過后,你想用到哪里都可以。與Perl的單步匹配相比,看起來多多做了些工作,但這并不很費事。  
             
            find()方法,就像你所想象的,用來搜索與正則表達式相匹配的任何目標字符串,group()方法,用來返回包含了所匹配文本的字符串。應注意的是,上面的代碼,僅用在每行只能含有一個匹配的電話號碼數字字符串時。可以肯定的說,java的正則表達式包能用在一行含有多個匹配目標時的搜索。本文的原意在于舉一些簡單的例子來激起讀者進一步去學習java自帶的正則表達式包,所以對此就沒有進行深入的探討。  
             
            這相當漂亮吧!   但是很遺憾的是,這僅是個電話號碼匹配器。很明顯,還有兩點可以改進。如果在電話號碼的開頭,即區位號和本地號碼之間可能會有空格。我們也可匹配這些情況,則通過在正則表達式中加入\s?來實現,其中?元字符表示在模式可能有0或1個空格符。  
             
            第二點是,在本地號碼位的前三位和后四位數字間有可能是空格符,而不是連字號,更有勝者,或根本就沒有分隔符,就是7位數字連在一起。對這幾種情況,我們可以用(-|)?來解決。這個結構的正則表達式就是轉換器,它能匹配上面所說的幾種情況。在()能含有管道符|時,它能匹配是否含有空格符或連字符,而尾部的?元字符表示是否根本沒有分隔符的情況。  
             
            最后,區位號也可能沒有包含在圓括號內,對此可以簡單地在圓括號后附上?元字符,但這不是一個很好的解決方法。因為它也包含了不配對的圓括號,比如" (555"   或   "555)"。相反,我們可以通過另一種轉換器來強迫讓電話號碼是否帶有有圓括號:(\(\d{3}\)|\d{3})。如果我們把上面代碼中的正則表達式用這些改進后的來替換的話,上面的代碼就成了一個非常有用的電話號碼數字匹配器:  
             
            Pattern   pattern   =  
             
            Pattern.compile("(\\(\\d{3}\\)|\\d{3})\\s?\\d{3}(-|)?\\d{4}");  
             
            可以確定的是,你可以自己試著進一步改進上面的代碼。  
             
            現在看看第二個例子,它是從Friedl的中改編過來的。其功能是用來檢查文本文件中是否有重復的單詞,這在印刷排版中會經常遇到,同樣也是個語法檢查器的問題。  
             
            匹配單詞,像其他的一樣,也可以通過好幾種的正則表達式來完成。可能最直接的是\b\w+\b,其優點在于只需用少量的regex元字符。其中\w元字符用來匹配從字母a到u的任何字符。+元字符表示匹配匹配一次或多次字符,\b元字符是用來說明匹配單詞的邊界,它可以是空格或任何一種不同的標點符號(包括逗號,句號等)。  
             
            現在,我們怎樣來檢查一個給定的單詞是否被重復了三次?為完成這個任務,需充分利用正則表達式中的所熟知的向后掃描。如前面提到的,圓括號在正則表達式中有幾種不同的用法,一個就是能提供組合類型,組合類型用來保存所匹配的結果或部分匹配的結果(以便后面能用到),即使遇到有相同的模式。在同樣的正則表達中,可能(也通常期望)不止有一個組合類型。在第n個組合類型中匹配結果可以通過向后掃描來獲取到。向后掃描使得搜索重復的單詞非常簡單:\b(\w+) \s+\1\b。  
             
            圓括號形成了一個組合類型,在這個正則表示中它是第一組合類型(也是僅有的一個)。向后掃描\1,指的是任何被\w+所匹配的單詞。我們的正則表達式因此能匹配這樣的單詞,它有一個或多個空格符,后面還跟有一個與此相同的單詞。注意的是,尾部的定位類型(\b)必不可少,它可以防止發生錯誤。如果我們想匹配"Paris   in   the   the   spring",而不是匹配"Java's   regex   package   is   the   theme   of   this   article"。根據java現在的格式,則上面的正則表達式就是:Pattern   pattern   =Pattern.compile("\\b(\\w+)\\s+\\1\\b");  
             
            最后進一步的修改是讓我們的匹配器對大小寫敏感。比如,下面的情況:"The   the   theme   of   this   article   is   the   Java's   regex   package.",這一點在regex中能非常簡單地實現,即通過使用在Pattern類中預定義的靜態標志CASE_INSENSITIVE   :  
             
            Pattern   pattern   =Pattern.compile("\\b(\\w+)\\s+\\1\\b",  
             
            Pattern.CASE_INSENSITIVE);  
             
            有關正則表達式的話題是非常豐富,而且復雜的,用Java來實現也非常廣泛,則需要對regex包進行的徹底研究,我們在這里所講的只是冰山一角。即使你對正則表達式比較陌生,使用regex包后會很快發現它強大功能和可伸縮性。如果你是個來自Perl或其他語言王國的老練的正則表達式的黑客,使用過 regex包后,你將會安心地投入到java的世界,而放棄其他的工具,并把java的regex包看成是手邊必備的利器。


          主站蜘蛛池模板: 临海市| 遂溪县| 彩票| 德钦县| 郑州市| 汉中市| 临漳县| 固安县| 安新县| 保山市| 麻江县| 黎川县| 万全县| 铅山县| 昂仁县| 梨树县| 咸丰县| 维西| 新民市| 罗定市| 怀仁县| 绩溪县| 大关县| 堆龙德庆县| 新泰市| 社旗县| 乐山市| 江川县| 博乐市| 威宁| 天津市| 宁安市| 长丰县| 清流县| 常熟市| 深水埗区| 马边| 桂平市| 阿拉善右旗| 乌拉特后旗| 黑龙江省|