Java API學(xué)習(xí)教程之正則表達(dá)式詳解
前言
正則表達(dá)式是什么應(yīng)該不用過多介紹,每位程序員應(yīng)該都知道,正則表達(dá)式描述的是一種規(guī)則,符合這種限定規(guī)則的字符串我們認(rèn)為它某種滿足條件的,是我們所需的。在正則表達(dá)式中,主要有兩種字符,一種描述的是普通的字符,另一種描述的是元字符。其中元字符是整個正則表達(dá)式的核心,并由它完成規(guī)則的制定工作。
本篇文章主要從Java這門程序設(shè)計語言的角度理解正則表達(dá)式的應(yīng)用,主要涉及以下內(nèi)容:
•基本正則表達(dá)式的理論基礎(chǔ)
•Java中用于正則表達(dá)式匹配的類
•幾種常用的正則表達(dá)式使用實例
一、正則表達(dá)式的理論基礎(chǔ)
1、普通字符的表示
我們說正則表達(dá)式主要由普通字符和元字符組成,那么我們首先先看看普通字符該如何表示。大部分普通字符由字符本身即可表示,例如:'s','i','n','g','l','e'等。除此之外,也有一些特殊的表示方式。
- 以/0開頭,后面緊跟1-3位數(shù)字,表示的是一個八進(jìn)制數(shù)。這個數(shù)的十進(jìn)制值對應(yīng)于ASCII編碼中的相應(yīng)字符。
- 以/x或者/X開頭,后面緊跟兩位字符,表示的是一個十六進(jìn)制的數(shù)。該數(shù)的十進(jìn)制的值對應(yīng)于ASCII編碼中相應(yīng)的字符。
- 以/u開頭,后面緊跟四位字符,表示一個Unicode編號。該編號對應(yīng)于Unicode字符集中的一個具體字符。
- 另外還有一些元字符,雖然它們具有特殊的含義,但是往往在某種特殊情況下,需要將這些元字符當(dāng)做普通字符使用,我們使用 '/'+元字符,表示轉(zhuǎn)移該元字符,此后該元字符將表示一個普通字符。例如:'//','/^',它們分別表示的是 '/'和 '^',不再具有特殊含義了。下面我們開始逐漸介紹正則表達(dá)式語法中的元字符的特殊含義。
2、字符組匹配單個字符
我們用一對中括號([.....])表示字符組,整個字符組中會有多個字符位列其中,該字符組表示的含義是:匹配任意一個字符,該字符是位列字符組中的。例如: [single]
匹配的是字符's','i','n','g','l','e'中的任意一個字符。以上我們簡單介紹了字符組的基本概念以及它所能匹配的內(nèi)容,其實有時候為了表述連續(xù)的字符,我們會結(jié)合元字符 '-' 一起來操作字符組。例如:[0123456789],匹配的是0到9之間的任意一個數(shù)字,對于這種情況我們可以選擇這樣來簡化操作:[0-9]。其實兩者表述的含義是一樣的,為了簡化起見,如果遇到連續(xù)的字符表述,可以選擇使用元字符來簡化。同樣的還有[a-z],它匹配任意一個小寫字母。對于元字符 '-' 還需要說明一點(diǎn)的是:該字符只有出現(xiàn)在兩個字符之間才具有特殊含義,單獨(dú)出現(xiàn)在字符組的所有字符之前或者之后只能表述普通字符 '-' 。下面介紹有關(guān)字符組的一些其他相關(guān)的元字符。
元字符 '^' 表示排除的意思,和元字符 '-' 類似,只有放在所有字符的最前面才具有特殊含義,否則只能表示普通字符。例如: [^1234]
,該字符組匹配一個字符,但是不是1或2或3或4。當(dāng)然, [c^yy]
,匹配的是四個普通字符,'c','^','y','y'。此外,需要注意一點(diǎn)的是,除了以上介紹的幾種元字符必須置放于指定位置上才能起作用以外,其余所有元字符在字符組中統(tǒng)統(tǒng)被視作普通字符,不再具有特殊含義。
除此之外,字符組還支持嵌套使用。例如: [0-9[a-z]]
,該字符組匹配一個數(shù)字或者一個字母。我們也可以使用&&加強(qiáng)限定規(guī)則。例如: [0-9&&[^0123]]
,該字符組匹配的是0到9之間任意一個數(shù)字,但是該數(shù)字不能是0到3中任意一個,也就是只能匹配4到9之間任意一個數(shù)字。最后和字符組有關(guān)的內(nèi)容還是涉及一個預(yù)定義字符組,所謂預(yù)定義字符組就是對字符組的適當(dāng)封裝,對于一些簡單的組合使用簡介的調(diào)用方式。例如:
•\d:等同于字符組 [0-9],表示任意一個數(shù)字字符
•\w:較為常見,等同于字符組[0-9a-zA-Z],表示任意一個world(單詞字符)
•\s:等同于[ \t\n\x0B\f\r]
,匹配的是一個空格字符(space)
當(dāng)然,它們也有相對應(yīng)的大寫形式,但是表示的意思卻是截然相反的。
•\D:等同于[^0-9]
,表示一個任意非數(shù)字字符
•\W:等同于[^0-9a-zA-Z]
,表示任意一個非單詞字符,往往會是一些特殊符號
•\S:等同于[^\t\n\x0B\f\r]
,匹配一個任意非空格的字符
3、用于指定字符多次出現(xiàn)的量詞
所謂的量詞主要是三個元字符,它們主要用于指定量詞前面的字符在匹配時可以多次出現(xiàn),具體區(qū)別接下來會介紹。首先我們需要知道,這三個元字符是:+ ,*, ?。下面描述它們各自作用及相互之間的區(qū)別:
•+:該元字符指定位于元字符前面的普通字符可以出現(xiàn)一次或者多次。例如:se+cyy這個正則表達(dá)式,字符secyy,seeeecyy都是可以匹配的,但是scyy是不能匹配的,前面的字符是必須出現(xiàn)的。
•*:該元字符指定位于元字符前面的普通字符可以出現(xiàn)零次或多次。例如:
se*cyy
對于該正則表達(dá)式而言,secyy,seecyy都是可匹配的,并且scyy也是可以匹配的。這就是和元字符 + 的簡單區(qū)別。
•?:該元字符指定位于元字符前面的普通字符可以出現(xiàn)也可以不出現(xiàn),但是不能多次出現(xiàn)。例如:se?cyy,對于該正則表達(dá)式,secyy,scyy等都是可匹配的,但是seeeecyy則是不能匹配的。它指定你前面的一個字符要么出現(xiàn),要么不出現(xiàn),不允許多次出現(xiàn)。
在這里我們要申明一個誤區(qū),這里的三個元字符量詞作用的是緊鄰該元字符前面的一個字符,并不是作用與元字符前面所有的字符,這里是需要注意的,包括筆者當(dāng)初也都是誤以為此的。
以上我們介紹了簡單量詞的概念,但是它們只能用于表示模糊的次數(shù)??梢猿霈F(xiàn)多次,但是多次是多少卻沒有定論。對于要求字符出現(xiàn)精確次數(shù)的情況,我們可以使用通用量詞來解決。{m,n}是通用量詞的最基本形式,它指定前面的字符出現(xiàn)的次數(shù)在m到n之間。
看幾個例子:
•se{0,10}cyy
:其中e可以出現(xiàn)0-10次
•se{9}cyy
:其中e必須出現(xiàn)9次
•se{0,}cyy
:其中e可以出現(xiàn)0-無窮大次,等同于se*cyy。
4、分組劃分組別
在介紹分組之前,無論是使用量詞還是字符組都是針對的一個字符。而分組針對的就是一串字符,我們也可以對分組使用量詞,控制該分組出現(xiàn)的次數(shù)。我們使用()括號表示分組,例如:
sing(le)de(cyy
其中l(wèi)e和cyy分別是一個分組,對于一個完整的正則表達(dá)式,從頭開始,每個分組都是有編號的,按照出現(xiàn)的次序,以1為基數(shù)遞增。至于為什么要有編號,下文說。對于分組我們依然是可以使用量詞控制其出現(xiàn)次數(shù)的,例如:
sing(le)+cccc:在該正則表達(dá)式中,分組le可以出現(xiàn)一次或者多次 sing(le)*cccc:在該正則表達(dá)式中,分組le可以出現(xiàn)零次或者多次
結(jié)合元字符 '| ',可以實現(xiàn)和字符組一樣的功效,例如:
(happy|cyy|single)
該正則表達(dá)式可以匹配三個字符子串,happy,cyy,single。但是這里需要注意的是,元字符 | 如果用于字符組中就不再具有特殊含義,將會被作為普通字符來匹配。(這一點(diǎn)其實在介紹字符組的時候已經(jīng)強(qiáng)調(diào)過)
下面解決一個上文遺留問題,分組的編號到底有什么作用。為分組編號其實是為了重新捕獲和使用分組,每個分組按照出現(xiàn)的次序從1開始遞增,我們使用 +分組編號進(jìn)行引用。
例如:
<(\w+)>(.*)</\1>:該正則表達(dá)式等效于:<(\w+)>(.*)</\w+>
(\w+)表示任意個字符(字母或數(shù)字),(.*)表示任意的符號,\1則引用了分組(\w+)。所以在這里,html中所有非單標(biāo)簽元素都是能匹配的。當(dāng)然,如果我們不想使用默認(rèn)的編號來引用分組,我們其實也是可以在定義分組的時候為分組命名。為分組命名的語法格式為: (?<name>X)
,引用分組的語法格式為: \k<name>
。例如:
<(?<num1>a)>(.*)</\k<num1>>:等效于:<a>(.*)</a>
上述正則表達(dá)式定義了一個名為num1的分組,并后續(xù)進(jìn)行了引用。下面介紹正則表達(dá)式的最后一塊理論基礎(chǔ),邊界匹配。
5、邊界匹配
以上我們所介紹的所有內(nèi)容主要還是針對單個字符或者多個字符組成的分組,我們可以限制他們的出現(xiàn)次數(shù)以及出現(xiàn)位置等。但是其實在正則表達(dá)式中,我們也是可以限制邊界必須滿足某種條件的。主要涉及的元字符有:^, $, \A, \Z, \z和\b。
首先看元字符 ^ ,在字符組中,該元字符表示否定的意思,此處匹配正則表達(dá)式首部位置邊界。例如: ^abc
匹配一個以abc開頭的字符串。
元字符 $匹配的字符串的尾部邊界,它規(guī)定被匹配的字符串必須以什么結(jié)束。例如:
abc$:dabc,abc,abc/n都是可匹配的
實際上,如果被匹配字符串是以指定字符結(jié)尾或者指定字符之后跟換行符,都是可匹配的。此處需要注意尾部邊界匹配時的表述格式。(不同于首部匹配)
\b匹配的是單詞邊界,所謂的單詞邊界指的就是:當(dāng)一邊是字符,一邊是非字符的時候,此處即為單詞邊界。也就是單詞結(jié)束的那個位置。還有一些邊界,例如:\A,\b,\Z等,各自匹配的邊界如下圖所示;
當(dāng)然,對于邊界匹配最通用的一種方式就是環(huán)視。它不局限于整個表達(dá)式的開頭和結(jié)尾,它可以出現(xiàn)在表達(dá)式中的任何位置,既可以向前匹配,也可以向后匹配。主要分為以下四種情況:
•肯定順序環(huán)視:它要求表達(dá)式的右邊字符串必須滿足某種約定,語法(?=....)
。例如:single(?=cyy)
,字符e的右邊即為邊界并且要求必須為cyy,所以該表達(dá)式只能匹配singlecyy。
•否定順序環(huán)視:它要求表達(dá)式的右邊字符串必須不能滿足某種約定,和上一中情況是相反的,語法格式為:(?!...)。
•肯定逆序環(huán)視:它要求表達(dá)式的左邊必須滿足某種約束,語法格式為:(?<=...)
。
•否定逆序環(huán)視:它要求表達(dá)式的左邊必須不能滿足某種約束,語法格式為:(?<!...)
。(此處為了消除!在MarkDown編輯器中的特殊樣式,加了空格,望讀者注意)
雖然看起來有四種不同的環(huán)視類型,但是實際上分為兩種,一種是向左看,一種是向右看。以上有關(guān)正則表達(dá)式的基本內(nèi)容大致介紹完結(jié),下面主要看看如何在Java中驗證我們上述的這些理論。
二、Java API對正則表達(dá)式的支持
在Java中,對正則表達(dá)式的支持,主要還是java.util.regex
這個包,我們常用的是其中的Pattern和Matcher這兩個類。其中Pattern綁定了一個正則表達(dá)式,也就是代表了一個規(guī)則,Matcher綁定了一個Pattern和一個被處理的字符串,我們可以利用Matcher中的一些方法來完成匹配工作。此外,Java中所有的正則表達(dá)式都是以字符串的形式出現(xiàn)的,所以自然離不開String這個類,該類中的很多方法的參數(shù)都是基于正則表達(dá)式的,下文將詳細(xì)介紹。我們首先看Pattern這個類。
Pattern主要用于編譯一個正則表達(dá)式,也就是創(chuàng)建一個Pattern對象,該對象與實際的一個正則表達(dá)式想綁定,它僅僅代表一個規(guī)則,與實際要匹配的字符串無關(guān)。
例如:
String str = "http://w"; Pattern p = Pattern.compile(str);
Pattern的compile方法將str這個正則表達(dá)式編譯成一種內(nèi)部結(jié)構(gòu),然后以Pattern實例的形式返回,至于這種內(nèi)部結(jié)構(gòu)是什么樣子的,此處暫時不涉及。我們只需要知道,此時返回的pattern實例是綁定了一個正則表達(dá)式的。當(dāng)然,Pattern還有一個compile重載,可顯式指定匹配模式。
public static Pattern compile(String regex, int flags) { return new Pattern(regex, flags); }
此處主要有四種匹配模式可選,單行模式,多行模式,無視大小寫模式,無視元字符模式(該模式下,所有元字符將會失效)。各自對應(yīng)的常量:Pattern.DOTALL,Pattern.MULTILINE,Pattern.CASE_INSENSITIVE,Pattern.LITERAL。這些常量的值如下:
public static final int DOTALL = 0x20;(32) public static final int MULTILINE = 0x08;(8) public static final int CASE_INSENSITIVE = 0x02;(2) public static final int LITERAL = 0x10;(16)
當(dāng)然,我們沒必要記住他們各自所對應(yīng)的常量的值,在使用的時候直接調(diào)用它們的常量名即可。下面通過介紹String的幾個基本方法,了解正則表達(dá)式在Java中的基本使用情況。
首先我們看split方法,該方法用于分割字符串,返回一個String數(shù)組。
public String[] split(String regex, int limit)
第一個參數(shù)接受的是一個正則表達(dá)式,第二參數(shù)用于限定分割次數(shù)。其實從其源代碼中我們大致可以知曉該方法作用原理:首先利用indexOf方法找到分割符首次出現(xiàn)的位置,將該位置以前所有字符保存,拿到剩余子串的所有內(nèi)容,一樣的操作。最后得到的數(shù)組就是按照分隔符分割的結(jié)果。limit只不過強(qiáng)制限定了分割次數(shù),達(dá)到次數(shù)上限,即使后面仍有分隔符可匹配,也選擇放棄。(打包后面所有內(nèi)容為一個分組),看個例子:
public static void main(String [] args){ String str = "cyy,single.abc/https"; String[] results = str.split("[,./]"); for(String s : results){ System.out.print(s+" "); } }
輸出結(jié)果:cyy single abc https
此處有人可能會有疑問,說好的Pattern和Matcher才是正則表達(dá)式的主要操作類,怎么沒見到他們。其實在split內(nèi)部調(diào)用的就是Pattern的相關(guān)方法。
return Pattern.compile(regex).split(this, limit);
這是String類中split方法的最后一行代碼,String中的split方法除了最后一行代碼,其余代碼處理的都是regex為普通單個字符的情況,而對于多個字符乃至包含元字符的時候都是由Pattern中split方法處理的,該方法中會創(chuàng)建Matcher類并調(diào)用其中find等方法進(jìn)行匹配查找,代碼量比較多,此處不再贅述。
下面看String的一個匹配校驗的方法。
public boolean matches(String regex) { return Pattern.matches(regex, this); }
顯然,該方法內(nèi)部調(diào)用的是Pattern的matches方法,
public static boolean matches(String regex, CharSequence input) { Pattern p = Pattern.compile(regex); Matcher m = p.matcher(input); return m.matches(); }
這是一個非常標(biāo)準(zhǔn)的對正則表達(dá)式的處理流程,首先編譯(綁定)正則表達(dá)式字符串獲取Pattern實例,然后調(diào)用Pattern的matcher方法獲取Matcher實例,接著就可以利用Matcher實例完成大量工作。此處調(diào)用matches方法完成對已綁定的正則表達(dá)式和預(yù)處理字符串的匹配工作,返回值為boolean。
最后看String的ReplaceAll方法:
public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); }
該方法實際上還是依賴的Matcher中的replaceAll方法,由于一個Matcher實例是同時綁定一個正則表達(dá)式和一個被匹配字符串的。所以在Matcher內(nèi)部的replaceAll方法在進(jìn)行搜索匹配的時候就無需傳入額外參數(shù)。具體代碼,大家可以自行查看,此處節(jié)約篇幅不再贅述。
三、常見正則表達(dá)式的案例
接下來我們主要從日常較為普遍使用的一些案例中來深刻理解上述所有內(nèi)容。
1、Email地址
通常我們的Email地址的格式主要是:
•3-18字符,可使用英文、數(shù)字、減號、點(diǎn)或下劃線
•必須以英文字母開頭,必須以英文字母或數(shù)字結(jié)尾
•點(diǎn)、減號、下劃線不能連續(xù)出現(xiàn)兩次或兩次以上
以上是騰訊QQ郵箱的要求,相對而言已算是較為復(fù)雜,接下來我么看如何實現(xiàn)它。首先,第一條要求:
[-._a-z0-9A-Z]{3,18}
滿足第二條要求:
[a-zA-Z][-._a-z0-9A-Z]{1,16}/w
滿足第三個條件:
(?![-0-9a-zA-Z._]*(--|\.\.|__))[a-zA-Z][-._a-z0-9A-Z]{1,16}/w
至于最后一個條件的匹配,我們使用否定順序環(huán)視來實現(xiàn),它要求右邊界所有內(nèi)容不能是如下的形式:0個或者多個(英文、數(shù)字、減號、點(diǎn)或下劃線)加上兩個連續(xù)減號或者點(diǎn)或者下劃線。也就是說,右邊如果由多個字符或者一個減號,點(diǎn)或者下劃線,那是沒事的,可一旦出現(xiàn)連續(xù)的減號,點(diǎn)或者下劃線,那么就將立馬被否定順序環(huán)視匹配,進(jìn)而不滿足條件結(jié)束。
其實上述對郵箱用戶名的匹配算是比較嚴(yán)格的,一般用于匹配郵箱用戶名的正則表達(dá)式則沒這么嚴(yán)格,具體要求如下:
•由英文字母、數(shù)字、下劃線、減號、點(diǎn)號組成
•至少1位,不超過64位
•開頭不能是減號、點(diǎn)號和下劃線
由于比較簡單,此處直接寫出結(jié)果:
[^-._][-._a-zA-Z0-9]{0,63}
2、手機(jī)號碼
在看一個手機(jī)號碼的正則表達(dá)式匹配情況,具體要求如下:
•中國的手機(jī)號碼都是11位數(shù)字
•目前手機(jī)號第1位都是1,第2位取值為3、4、5、7、8之一
最終的表述結(jié)果為:
1[34578][0-9]{9}
還有很多案例,此處為了使篇幅不至過長,到此為止。更多案例還有待大家自行測試。
總結(jié)
以上就是這篇文章的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Mybatis實現(xiàn)動態(tài)SQL編寫的示例詳解
這篇文章主要為大家詳細(xì)介紹了mybatis中的動態(tài)sql的使用以及緩存的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,需要的可以參考一下2023-02-02Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解
這篇文章主要介紹了Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解,在Java中,我們可以通過Executors類來創(chuàng)建ScheduledThreadPool,Executors類提供了幾個靜態(tài)方法來創(chuàng)建不同類型的線程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09idea數(shù)據(jù)庫驅(qū)動下載失敗的問題及解決
這篇文章主要介紹了idea數(shù)據(jù)庫驅(qū)動下載失敗的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01IntelliJ IDEA 中g(shù)it的使用圖文教程
本文通過圖文并茂的形式給大家介紹了IntelliJ IDEA 中g(shù)it的使用,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-02-02