正則表達(dá)式初探

前言

最近工作中涉及到一些使用正則表達(dá)式的地方,雖然以前也遇到過這種情況,但是我的處理方式一般都是百度一下...看一下網(wǎng)上有成型的解決方式就拿來直接用了雪情。從來沒有認(rèn)真的系統(tǒng)的了解正則表達(dá)式的語法結(jié)構(gòu)和相關(guān)知識状婶,這次在百度查詢的時候偶然間看到 deerchao 大神寫的關(guān)于正則表達(dá)式的一系列內(nèi)容,讓自己對這部分內(nèi)容有了一個相對比較全面的了解伏蚊,希望用一篇筆記整理一下自己的理解立轧,并供以后查閱,當(dāng)然如果能夠幫助到看到這篇文章的你躏吊,我會感到不勝榮幸氛改。鑒于個人水平有限,文中若有錯誤比伏,請您諒解并不吝指正胜卤。

正則表達(dá)式是什么玩意?

其實當(dāng)我們初步開始接觸一門語言或者開發(fā)技術(shù)的時候赁项,我們都無可避免的了解一些關(guān)于字符串處理的相關(guān)方法或者函數(shù)葛躏,比如 OC 中的 NSString 相關(guān)的方法:

NSString* str = [NSString stringWithFormat:@"%@", userId];

以及 Java 中的 string 相關(guān)的方法:

str.trim();

一般情況下,當(dāng)然我是說我 =悠菜。= 舰攒,這個時候我們就會認(rèn)為關(guān)于字符串處理的東西我們了解的夠多了,系統(tǒng)提供的 API 已經(jīng)足夠應(yīng)付我們遇到的所有問題了悔醋,然而在實際的開發(fā)工作中我們會經(jīng)常遇到的場景是去查找符合某些復(fù)雜規(guī)則的字符串的需要摩窃,或者從一個字符串中提取出符合某些 規(guī)則 的子串,這個時候僅僅依靠系統(tǒng)的 API 是很難完成這個任務(wù)(最起碼付出的時間成本很高)篙顺,需要配合使用的 規(guī)則 便是我們今天要聊的 正則表達(dá)式 偶芍。

在編寫處理字符串的程序或網(wǎng)頁時,經(jīng)常會有查找符合某些復(fù)雜規(guī)則的字符串的需要德玫。正則表達(dá)式就是用于描述這些規(guī)則的工具匪蟀。換句話說,正則表達(dá)式就是記錄文本規(guī)則的代碼宰僧。

正則表達(dá)式的語法

其實最簡單的正則匹配我們都會材彪,笨蛋其實就是‘相等’嘛,比如說:我們想要在一個長長的字符串中將 hello 提取出來,第一種方法不需要使用正則段化,我們可以遍歷字符串嘁捷,截取這個 range 下的子串,并一一與"hello"進(jìn)行對比显熏,相同的就取出來雄嚣;還有另外的方法就是使用正則表達(dá)式進(jìn)行匹配截取,當(dāng)然這個時候的正則表達(dá)式就是最簡單的正則喘蟆,使用hello就可以了缓升。

當(dāng)然,我們在開發(fā)中遇到的字符串匹配的場景并不會永遠(yuǎn)這樣簡單蕴轨,比如說:還是剛剛的那個場景港谊,只不過我需要提取的是 hello 這個單詞,那么剛剛的使用正則的匹配就會將 ahellob 當(dāng)中的 hello 也會匹配出來橙弱,而這并不是我所希望歧寺,這個時候我們就需要學(xué)習(xí)以下的一些內(nèi)容。

元字符

元字符是幫助我們在書寫正則表達(dá)式的時候棘脐,代表一個位置或者一個(或多個)字符而存在的斜筐,比如回到上面的例子,我需要精確的獲得 hello 這個單詞荆残,并且保證我獲得的 hello 不是在其他的單詞當(dāng)中奴艾,不難分析的是,我需要一個玩意來讓我告訴正則匹配内斯,h 的前面是單詞的開始并且 o 的后面是單詞的結(jié)束蕴潦,這個時候我們需要用到\\b,相應(yīng)的正則表達(dá)式變?yōu)?code>\\bhello\\b俘闯,\\b是代表 匹配單詞的開始或結(jié)束 的元字符潭苞。
我們常見的元字符主要包括:

代碼 元字符說明
. 匹配除換行符以外的任意字符
\w 匹配字母或數(shù)字或下劃線或漢字
\s 匹配任意的空白符
\d 匹配數(shù)字
\b 匹配單詞的開始或結(jié)束
^ 匹配字符串的開始
$ 匹配字符串的結(jié)束

重復(fù)限定符

有的時候需要表示有多個同一類型的字符出現(xiàn)的時候就需要使用限定符來進(jìn)行限定,比如需要匹配兩個數(shù)字真朗,就需要使用\\d{2}此疹,以下是常見的限定符號:

代碼 限定符說明
* 重復(fù)零次或更多次
+ 重復(fù)一次或更多次
? 重復(fù)零次或一次
{n} 重復(fù) n 次
{n,} 重復(fù) n 次或更多次
{n,m} 重復(fù) n 次到 m 次

反義

反義的理解比較簡單,其實就是元字符表示的反義遮婶,舉個例子就很好理解了:比如說想要查找查找除了數(shù)字以外蝗碎,其它任意字符都行的情況。

代碼 反義符說明
\W 匹配任意不是字母或數(shù)字或下劃線或漢字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意不是數(shù)字的字符
\B 匹配不是單詞的開始或結(jié)束的位置
[^x] 匹配x以外的任意字符
[^aeiou] 匹配除了aeiou這幾個字母以外的任意字符

字符類

當(dāng)我學(xué)習(xí)到上面的那些的時候旗扑,我已經(jīng)覺得自己無所不能了蹦骑。

好吧,我承認(rèn)我就是一個牛皮匠=臀防。=

對于數(shù)字眠菇、字母或數(shù)字边败、空白,這些已經(jīng)有對應(yīng)的元字符表示這些字符集合捎废,書寫正則進(jìn)行匹配的時候還是比較簡單的笑窜,但是如果說我們想要匹配的事 10以內(nèi)的奇數(shù) ,那么原來的提供的那些元字符就不那么容易能夠完成任務(wù)了登疗。
其實正則表達(dá)式的語法中給我們提供了可以自定義字符集的方法排截,在方括號里面將你需要的字符列出來就行了,比如上面的例子中只要使用[13579]就可以了谜叹。

分支條件

分支條件我的理解簡單來說就是編程語言中的 邏輯或 匾寝,下面是一段引用的正式解釋:

正則表達(dá)式里的分枝條件指的是有幾種規(guī)則,如果滿足其中任意一種規(guī)則都應(yīng)該當(dāng)成匹配荷腊,具體方法是用 | 把不同的規(guī)則分隔開。

比如美國郵編的規(guī)則是5位數(shù)字急凰,或者用連字號間隔的9位數(shù)字女仰,那么我們可以使用分支條件處理:\\d{5}-\\d{4}|\\d{5}

需要注意的是:使用分支條件的時候需要特別注意條件的先后順序,原因是匹配分枝條件時抡锈,將會從左到右地測試每個條件疾忍,如果滿足了某個分枝的話,就不會去再管其它的條件了床三。

例如上述例子一罩,如果改成\\d{5}|\\d{5}-\\d{4}的話,就只能夠匹配5位的郵編(以及9位郵編的前5位)撇簿。

分組

前面提到的重復(fù)限定符已經(jīng)讓我們知道了如何對單個字符進(jìn)行重復(fù)表達(dá)(在字符后面加上重復(fù)限定符聂渊,如\\d+),但是考慮到現(xiàn)實應(yīng)用中我們會遇到對多個字符進(jìn)行重復(fù)表達(dá)四瘫,比如:常見的 IP 地址汉嗽,IP 地址的大致樣子為 3位數(shù)字加上一個點 重復(fù)三次,再加上一個三位數(shù)字找蜜,這里需要用到分組表達(dá)饼暑,用小括號來指定子表達(dá)式(也叫做分組),然后你就可以指定這個子表達(dá)式的重復(fù)次數(shù)了洗做,正則表達(dá)式為:(\\d{1,3}\\.){3}\\d{1,3}

當(dāng)然弓叛,上面的表達(dá)式最終可能會匹配出不符合 IP 地址規(guī)范的地址,如:999.999.999.999诚纸,只能使用冗長的分組撰筷,選擇,字符類來描述一個正確的IP地址:
((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)

關(guān)于分組咬清,還會引申出更多的一些細(xì)節(jié)之處:

后向引用

當(dāng)我們在正則表達(dá)式中使用分組的時候闭专,如果分組匹配到內(nèi)容(即小括號內(nèi)的子表達(dá)式匹配的內(nèi)容)奴潘,正則匹配引擎會記錄這段內(nèi)容,并分配組號影钉,這個過程我們可以形象的稱之為 捕獲 画髓。

書寫復(fù)雜的正則的時候可能會需要對分組捕獲的內(nèi)容進(jìn)行進(jìn)一步的處理,后向引用就是對前面某個分組匹配的內(nèi)容進(jìn)行進(jìn)一步的處理平委。
在這里最簡單的例子就是重復(fù)的兩個單詞放在一起奈虾,比如:hello hello 或者 hi hi ,注意兩個單詞之間有若干空格廉赔,配這種字符串的正則表達(dá)式為:\\b(\\w+)\\b\\s+\\1\\b肉微,其中\\1就代表分組1匹配到的內(nèi)容。

關(guān)于分組組號的分配這里有一些補充:

1蜡塌、分組0對應(yīng)整個正則表達(dá)式碉纳;
2、實際上組號分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配馏艾,第二遍只給命名組分配--因此所有命名組的組號都大于未命名的組號劳曹;
3、可以使用(?:exp)這樣的語法來剝奪一個分組對組號分配的參與權(quán)琅摩;
4铁孵、可以給分組添加一個自定義的組號:(?<name>exp),name 就是這個分組的組號房资,其中的<name>可以替換成'name'

分組中捕獲相關(guān)的常用語法:

代碼 反義符說明
(exp) 匹配exp,并捕獲文本到自動命名的組里
(?<name>exp) 匹配exp,并捕獲文本到名稱為name的組里蜕劝,也可以寫成(?'name'exp)
(?:exp) 匹配exp,不捕獲匹配的文本,也不給此分組分配組號

零寬斷言和負(fù)向零寬斷言

這兩個概念的名稱表面上看是非常難以揣測意思的轰异,并且非常拗口岖沛。
其實無論是零寬斷言還是負(fù)向零寬斷言都是代表的一個位置,就像前面介紹過的\\b 溉浙、^$烫止,零寬 的含義就是零寬度的一個位置,并且 斷言 這個位置滿足一定的條件戳稽,零寬斷言是用來查找在 某些內(nèi)容 之前或者之后的 一些東西馆蠕,并且不包括 這些內(nèi)容 ;而負(fù)向零寬斷言與零寬斷言是相反的關(guān)系惊奇,它用來匹配 一些東西 的前面或之后不是 某些內(nèi)容互躬,并且不包括 這些內(nèi)容

相關(guān)的常用語法:

代碼/語法 反義符說明
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面內(nèi)容不是exp的位置
(?<!exp) 匹配前面內(nèi)容不是exp的位置

下面我們通過幾個例子逐個對上面的語法進(jìn)行一下應(yīng)用:

  • (?=exp)即零寬度正預(yù)測先行斷言颂郎,它斷言自身出現(xiàn)的位置的后面能匹配表達(dá)式exp吼渡。比如\\b\\w+(?=ing\\b),匹配以ing結(jié)尾的單詞的前面部分(除了ing以外的部分)乓序,如查找I'm singing while you're dancing.時寺酪,它會匹配sing和danc坎背;
  • (?<=exp)即零寬度正回顧后發(fā)斷言,它斷言自身出現(xiàn)的位置的前面能匹配表達(dá)式exp寄雀。比如(?<=\\bre)\\w+\\b會匹配以re開頭的單詞的后半部分(除了re以外的部分)得滤,例如在查找reading a book時,它匹配ading盒犹;
  • (?!exp)即零寬度負(fù)預(yù)測先行斷言懂更,斷言此位置的后面不能匹配表達(dá)式exp。例如:\\d{3}(?!\\d)匹配三位數(shù)字急膀,而且這三位數(shù)字的后面不能是數(shù)字沮协;\\b((?!abc)\\w)+\\b匹配不包含連續(xù)字符串a(chǎn)bc的單詞。
  • (?<!exp)即零寬度負(fù)回顧后發(fā)斷言卓嫂,斷言此位置的前面不能匹配表達(dá)式exp慷暂。(?<![a-z])\\d{7}匹配前面不是小寫字母的七位數(shù)字。

貪婪和懶惰原則

當(dāng)正則表達(dá)式中包含接受重復(fù)的限定符時命黔,匹配的過程中默認(rèn)會在滿足表達(dá)式整體得到匹配的前提下匹配盡可能多的字符呜呐,這種行為被稱作 貪婪匹配
比如對于字符串 aabab 悍募,如果使用表達(dá)式a.*b進(jìn)行匹配,匹配的結(jié)果將會是以a開始洋机,以b結(jié)束的最長的字符串坠宴,即 aabab 本身。

如果我們需要匹配盡可能少的字符绷旗,那么我們就需要懶惰匹配喜鼓,在重復(fù)限定符的后面加上?,就代表進(jìn)行懶惰匹配衔肢,剛剛的例子中庄岖,如果我們使用a.*?進(jìn)行匹配的話,匹配結(jié)果會是aab(第一到第三個字符)和ab(第四到第五個字符)角骤。

在這里補充一下:為什么第一個匹配是aab(第一到第三個字符)而不是ab(第二到第三個字符)隅忿?簡單地說,因為正則表達(dá)式有另一條規(guī)則邦尊,比懶惰/貪婪規(guī)則的優(yōu)先級更高:最先開始的匹配擁有最高的優(yōu)先權(quán)——The match that begins earliest wins背桐。

懶惰限定符的常見語法:

代碼 說明
*? 重復(fù)任意次,但盡可能少重復(fù)
+? 重復(fù)1次或更多次蝉揍,但盡可能少重復(fù)
?? 重復(fù)0次或1次链峭,但盡可能少重復(fù)
{n,m}? 重復(fù)n到m次,但盡可能少重復(fù)
{n,}? 重復(fù)n次以上又沾,但盡可能少重復(fù)

在 iOS 開發(fā)中使用正則表達(dá)式

正則的基本語法在所有的正則引擎中基本差不多弊仪,貌似看有的語言使用的正則引擎對零寬斷言和負(fù)向零寬斷言不支持熙卡,使用 Swift 測試了一下,應(yīng)該 iOS 還是支持零寬斷言和負(fù)向零寬斷言的正則語法励饵。

在 iOS 中常見的正則表達(dá)式的使用方式有三種:

  1. 在 NSPredicate 中使用正則表達(dá)式:
 - (BOOL)validateNumber:(NSString *) textString
{
    NSString* number=@"^[0-9]+$";
    NSPredicate *numberPre = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",number];
    return [numberPre evaluateWithObject:textString];
}
  1. NSString 方法中使用:
NSString *searchText = @"rangeOfString";
NSRange range = [searchText rangeOfString:@"^[0-9]+$" options:NSRegularExpressionSearch];
if (range.location != NSNotFound) {
   NSLog(@"range :%@", [searchText substringWithRange:range]);
}
  1. NSRegularExpression:
    //1. 要匹配的字符串
    NSString *str = @"I'm singing while you're dancing";
    //Pattern: 正則表達(dá)式語句
    NSString *pattern = @"\\\\b\\\\w+(?=ing\\\\b)";
    //2. 創(chuàng)建正則表達(dá)式
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
    //3. 匹配結(jié)果
    NSArray *result = [regex matchesInString:str options:NSMatchingReportCompletion range:NSMakeRange(0, str.length)];
    // 打印結(jié)果
    if (result.count == 0) {
        NSLog(@"匹配出錯");
    } else {
        NSLog(@"匹配成功: %lu",(unsigned long)result.count);
        NSLog(@"%@", result);
    }

同樣的代碼也用 Swift 進(jìn)行了測試驳癌,結(jié)果一致:

    // 定義正則表達(dá)式語句
    let pattern = "\\\\b\\\\w+(?=ing\\\\b)"
    // 定義正則表達(dá)式對象
    let regex = try! NSRegularExpression(pattern: pattern, options: .CaseInsensitive)
    // 待匹配的字符串
    let str = "I'm singing while you're dancing"
    // 進(jìn)行匹配
    let arr: [NSTextCheckingResult] = regex.matchesInString(str , options: NSMatchingOptions.ReportCompletion, range: NSMakeRange(0, str.characters.count))
    print(arr)

在我個人的日常使用中,還是第三種用的稍微多一些曲横,NSPredicate 基本用的機會很少喂柒,也不是太了解,后續(xù)有機會了解的話會補充到本篇博客中禾嫉。

結(jié)尾

  1. 本篇博客中只是羅列了正則表達(dá)式中較為基礎(chǔ)的部分語法灾杰,其實有許多東西我也是邊學(xué)習(xí)邊總結(jié),很多語法也不是特別清楚熙参,后面有補充的會一點一點補充到這篇博客中艳吠;
  2. 正則所涵蓋的語法點非常多也比較瑣碎,正則表達(dá)式書寫出來也比較的繞而且亂七八糟的符號也比較亂孽椰,所以需要不斷的練習(xí)和使用才能靈活的解決開發(fā)中的實際問題昭娩,就我個人而言也是需要加強練習(xí),改掉工作中依賴搜索的懶毛彩蜇摇(前提是開發(fā)進(jìn)度允許=栏渺。=);

最后锐涯,祝大家玩的愉快磕诊!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纹腌,隨后出現(xiàn)的幾起案子霎终,更是在濱河造成了極大的恐慌,老刑警劉巖升薯,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莱褒,死亡現(xiàn)場離奇詭異,居然都是意外死亡涎劈,警方通過查閱死者的電腦和手機广凸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來责语,“玉大人炮障,你說我怎么就攤上這事±ず颍” “怎么了胁赢?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長白筹。 經(jīng)常有香客問我智末,道長谅摄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任系馆,我火速辦了婚禮送漠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘由蘑。我一直安慰自己闽寡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布尼酿。 她就那樣靜靜地躺著爷狈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裳擎。 梳的紋絲不亂的頭發(fā)上涎永,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音鹿响,去河邊找鬼羡微。 笑死,一個胖子當(dāng)著我的面吹牛惶我,可吹牛的內(nèi)容都是我干的妈倔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绸贡,長吁一口氣:“原來是場噩夢啊……” “哼启涯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恃轩,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黎做,沒想到半個月后叉跛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蒸殿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年筷厘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏所。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡酥艳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爬骤,到底是詐尸還是另有隱情充石,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布霞玄,位于F島的核電站骤铃,受9級特大地震影響拉岁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惰爬,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一喊暖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撕瞧,春花似錦陵叽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硼婿,卻和暖如春锌半,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寇漫。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工刊殉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人州胳。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓记焊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親栓撞。 傳聞我的和親對象是個殘疾皇子遍膜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容