斷言
本文目的是講解正則表達式之斷言用法。目前網際網路上有很多博文對斷言講解的並不透徹,如果您剛開始學習斷言,相信此博文會對您有幫助。
1.2.3.1 情景導入
假設,我要獲得一個字符串裡面所有以空格開頭的英文詞語。
在使用上面的代碼過程中存在一些問題,我獲得的單詞默認是前頭帶空格的,縱然我可以通過String類的trim來解決這些問題,可是這樣用起來會很麻煩,有沒有一種正則表達式的規則可以檢測那些開頭帶空格的詞彙但是適配到的詞彙不帶空格呢?答案是有的,那就是斷言
什麼是斷言
在使用正則表達式時,有時我們需要捕獲的內容前後必須是特定內容,但又不捕獲這些特定內容的時候,零寬斷言就起到作用了。
斷言全稱為零寬斷言
斷言的語法規則
零寬斷言為什麼叫零寬斷言
理解這一點至關重要,拿零寬前行正向斷言來舉例
零寬
零寬的含義為斷言部分不佔據寬度,舉個例子12(?=la)匹配12la匹配到的是12,含義是la前面的12。
12la匹配12la匹配到的是12la,含義為匹配value為12la的字符串。不知道通過這個例子你能否看懂零寬的意義,不懂也沒關係,看完接下來幾章就會懂了。
前行
前行的含義我可以確定我說的是正確的,現在網際網路上好多人對前行的理解是錯誤的,雖然按照他們的解釋是可以解釋的通大多數情況,但是我們還是要抱著實事求是的態度,還原前行真正的含義,參考網站:正則表達式零寬斷言詳解(?=,?<=,?!,?,Regular expression to match a line that doesn't contain a word。
在正則表達式中,每個字符串都是有定位的,所謂定位,就是確定字符串中每個字符位置的數字,這個只可意會,我給您看幾張圖,通過圖片,或許可以理解
看到沒有,字符串每一個字符間隔就是一個個位置點,字符串第一個字符前為位置0,字符串第一個字符和第二個字符之間為位置1,字符串第一個字符位於位置0和位置1之間,以此類推……
前行的意義為位置之前,後行的意義為位置之後
如上圖,位置5位於紅色區域後行,位置5位於黃色區域前行
那麼,在斷言中前行和後行代表著什麼呢?
比如說正則表達式12(?=la)在匹配字符串qwew12bb12la的時候,他會先,匹配到從左往右第一個12,如下圖所示
因為(?=x)是前行匹配,這個時候12是佔位置的,位置點走到了第一個12的後面,即位置6,。因為是零寬,(?=la)不佔位置,接下來以位置6為前行,匹配位置6的後面,位置6的後面不是la,故不匹配位置6前面的12,接下來位置轉到了第二個12後面,如下圖所示
因為是前行匹配,位置10作為前行,匹配後面的字符,後面是la符合要求,因為零寬,匹配到的最終字符不包括零寬,如下圖所示
不懂也沒關係,看完接下來幾章就會懂了。
負向
所謂負向,不符合為負向,如(?<!x)
就是負向,該斷言匹配的是不符合x標準的字符串作為目標字符串的後行
再來熟悉一下幾個斷言
斷言DEMO
後行正向斷言,該斷言匹配前面是aa的字符
運行結果為
dfdsfs
後行負向斷言,該斷言匹配的是前面不是$的數字
運行結果為
matcher1.group() = 23
我來解釋一下,正則表達式(?<!\\$|\\d)\\d+,斷言的意思是不匹配前面為$或數字的數字。
模擬一下匹配的過程,也許會更加明白是怎麼一回事兒,先說一下"$14 23",位置我就不在圖中標了,自己數一下吧
(?<!\\$|\\d)\\d+先匹配數字,匹配到了1,位置是1,因為斷言是不佔寬度的。因為是後行,檢測到左邊是,符合要求,故匹配23
先行正向斷言
運行結果是
matcher2.group() = windows
我來稍微解釋一下,"(?i)windows(?=2000|xp|10)"(?i)表明不區分大小寫,Windows表明適配windows,(?=2000|xp|10)表明是零寬先行正向斷言。我說一下匹配的過程,首先匹配windows,位置來到了windows後,零寬先行,後面是xp適配成功。
先行負向斷言
運行結果是
matcher3.group() = linux
這個就不解釋了,自行理解吧
以上四個demo是最最基礎的demo,在很多教程上對斷言的講解止步於此(實際上,很短教程連最基本的匹配過程都沒講,只講了匹配的結果,這個要出大問題的),務須掌握好上面的四個demo。
斷言的基礎應用和實際用處
放一張思維導圖
這個我先說一下結論,在實際應用開發中,在應用諸如驗證開頭或結尾(只要求一行的開頭或結尾)的實踐中,大概有兩種,一種是零寬,一種是非零寬。所謂零寬即匹配的數據不包括斷言,非零寬反之,在某些情況下需要藉助String方法的subString()方法。
一般情況下,使用斷言,主要是用它零寬的屬性,驗證開頭包含,結尾包含時一般不用斷言,分組就能解決問題,當開頭不包含或結尾不包含等問題時,使用斷言是很好的辦法。
演示三個
驗證不包含
運行結果如下
驗證不包含 matcherError.group() = wqeqr12eqr34512 !"wqer12335".contains("123") = false
解析:正則表達式裡字符串"不包含"匹配技巧 | 外刊IT評論
驗證開頭包含
一行字符串,驗證開頭是否包含某字符串,若包含則輸出匹配到的數據,此方法會包括斷言。也就是說假如驗證「123erre」的開頭是否是123,匹配到的數據為123erre而不是erre。驗證開頭包含且匹配數據不包括斷言請看下一章
結果如下
驗證開頭包含 falsematcherRight.group() = wercdv
解析:
^(?=wer).*匹配wercdv的時候,先因為^來到了位置0,位置0後面是符合.*的,遂開始匹配斷言,注意這時候位置依然在位置0,因為是先行斷言,位置0後面是wer符合斷言要求,故匹配到了wercdv
驗證開頭包含且匹配到的數據不包括斷言
假如說要匹配開頭是wer的字符串,字符串是wercdv,匹配到的是cdv而不是wercdv
結果如下
驗證開頭包含,匹配到的數據不包括斷言false matcherRight.start() = 0matcherRight.end() = 6matcherRight.group() = cdv
解析:
^wer(?<=wer)(.*)因為^所以先來到了位置0,然後匹配wer,來到了位置3,位置3後面符合.*的要求,因為後行匹配,前面是wer,符合要求,在取出的過程中,有一個分組,選取的是.*故最終取出cdv
驗證結尾包含,且匹配到的數據不包括斷言
舉個例子,匹配以「元」結尾的詞彙,且匹配到的數據不包括「元」,譬如「12元」,匹配到的數據是12。
運行結果是
驗證結尾包含,且獲得結尾之前的數字matcherRight.group(1) = 12
解析:
(\\d+)(?=元).$因為$先來到位置3,如圖所示
然後因為(\\d+)(?=元).$的.,位置向前一步,來到了位置2,然後前行斷言,位置2的後面是元,前面是數字符合要求,如下圖所示
匹配到了「12元」,因為輸出組①,故最終輸出12。
斷言基礎應用總體代碼
代碼文件,運行結果寫在了注釋裡面
不按套路出牌,幫你徹底理解斷言
一般情況下,使用斷言,主要是用它零寬的屬性,向驗證開頭包含,結尾包含時一般不用斷言,分組就能解決問題,當開頭不包含或結尾不包含等問題時,使用斷言是很好的辦法。
絕大多數網站在講解斷言問題的時候總是去引用斷言demo一章裡面那四個例子,這會給人造成一種「前行斷言適配結尾,後行斷言適配開頭」的錯覺,事實上並不是這個樣子,重要的是要具體問題具體分析。如同上面的代碼,不按所謂常理出牌,有助於正則表達式之理解。
上面取了四個例子,皆不按套路來,為方便理解,選一個細講
運行結果
不按套路來的零寬後行負向斷言 matcher3.group() = lla
(?i)\\w+(?<!re)a的本意是不區分大小寫地去適配結尾是a且a的前面不是re的字符。(?!)是不區分大小寫的意思。正則發揮作用時,先找到a,位置來到a的前面,因為後行斷言,位置的前面不能是re。因為\\w+,a的前面需要是字母或數字。綜合看來,意思是a的前面是除re之外的字母或數字。
如果真的不理解,就死記下面的實例
不包含a
開頭不包含a
結尾不包含a
包含a且不包含b
處於a和b之間
適配字符串中所有不以b結尾的數字
適配字符串中所有以a開頭的數字
在某些時候亦可以用\b來實現某些可以用斷言實現的正則表達,舉個例子
獲取所有以b結尾的數字
查看代碼: