筆記:如何寫出高效率的正則表達式(轉)

原文地址是:http://iregex.org/blog/regex-optimizing.html

 

如果純粹是爲了挑戰自己的正則水平,用來實現一些特效(例如使用正則表達式計算質數、解線性方程),效率不是問題;如果所寫的正則表達式只是爲了滿 足一兩次、幾十次的運行,優化與否區別也不太大。但是,如果所寫的正則表達式會百萬次、千萬次地運行,效率就是很大的問題了。我這裏總結了幾條提升正則表 達式運行效率的經驗(工作中學到的,看書學來的,自己的體會),貼在這裏。如果您有其它的經驗而這裏沒有提及,歡迎賜教。

爲行文方便,先定義兩個概念。

  • 誤匹配 :指正則表達式所匹配的內容範圍超出了所需要範圍,有些文本明明不符合要求,但是被所寫的正則式“擊中了”。例如,如果使用/d{11} 來匹配11位的手機號,/d{11} 不單能匹配正確的手機號,它還會匹配98765432100 這樣的明顯不是手機號的字符串。我們把這樣的匹配稱之爲誤匹配。
  • 漏匹配 :指正則表達式所匹配的內容所規定的範圍太狹窄,有些文本確實是所需要的,但是所寫的正則沒有將這種情況囊括在內。例如,使用/d{18} 來匹配18位的身份證號碼,就會漏掉結尾是字母X的情況。

寫出一條正則表達式,既可能只出現 誤匹配(條件寫得極寬鬆,其範圍大於目標文本),也可能只出現 漏匹配(只描述了目標文本中多種情況種的一種),還可能既有誤匹配又有漏匹配 。例如,使用/w+/.com 來匹配.com結尾的域名,既會誤匹配abc_.com 這樣的字串(合法的域名中不含下劃線,/w 包含了下劃線這種情況),又會漏掉ab-c.com 這樣的域名(合法域名中可以含中劃線,但是/w 不匹配中劃線)。

精準的正則表達式意味着既無誤匹配且無漏匹配。當然,現實中存在這樣的情況:只能看到有限數量的文本,根據這些文本寫規則,但是這些規則將會用到海 量的文本中。這種情況下,儘可能地(如果不是完全地)消除誤匹配以及漏匹配,並提升運行效率,就是我們的目標。本文所提出的經驗,主要是針對這種情況。

  1. 掌握語法細節 。正則表達式在各種語言中,其語法大致相同,細節各有千秋。明確所使用語言的正則的語法的細節,是寫出正確、高效正則表達式的基礎。例如,perl中與/w 等效的匹配範圍是[a-zA-Z0-9_] ;perl正則式不支持肯定逆序環視中使用可變的重複(variable repetition inside lookbehind,例如(?<=.*)abc ),但是.Net語法是支持這一特性的;又如,JavaScript連逆序環視(Lookbehind,如(?<=ab)c )都不支持,而perl和python是支持的。《精通正則表達式》第3章《正則表達式的特性和流派概覽》明確地列出了各大派系正則的異同,這篇文章 也簡要地列出了幾種常用語言、工具中正則的比較。對於具體使用者而言,至少應該詳細瞭解正在使用的那種工作語言里正則的語法細節。
  2. 先粗後精, 先加後減 。使用正則表達式語法對於目標文本進行描述和界定,可以像畫素描一樣,先大致勾勒出框架,再逐步在局步實現細節。仍舉剛纔的手機號的例子,先界定/d{11} ,總不會錯;再細化爲1[358]/d{9} , 就向前邁了一大步(至於第二位是不是3、5、8,這裏無意深究,只舉這樣一個例子,說明逐步細化的過程)。這樣做的目的是先消除漏匹配(剛開始先儘可能多 地匹配,做加法),然後再一點一點地消除誤匹配(做減法)。這樣有先有後,在考慮時纔不易出錯,從而向“不誤不漏”這個目標邁進。
  3. 留有餘地 。所能看到的文本sample是有限的,而待匹配檢驗的文本是海量的,暫時不可見的。對於這樣的情況,在寫正則表達式時要 跳出所能見到的文本的圈子,開拓思路,作出“戰略性前瞻”。例如,經常收到這樣的垃圾短信:“發*票”、“發#漂”。如果要寫規則屏蔽這樣煩人的垃圾短 信,不但要能寫出可以匹配當前文本的正則表達式 發[*#](?:票|漂) ,還要能夠想到 發.(?:票|漂|飄) 之類可能出現的“變種”。這在具體的領域或許會有針對性的規則,不多言。這樣做的目的是消除漏匹配,延長正則表達式的生命週期。
  4. 明確 。具體說來,就是謹慎 用點號這樣的元字符,儘可能 不用星號和加號這樣的任意量詞。只要能確定範圍 的,例如/w,就不要用點號;只要能夠預測重複次數的,就不要用任意量詞。例如,寫析取twitter消息的腳本,假設一條消息的xml正文部分結構 是<span class=”msg”>…</span>且正文中無尖括號,那麼<span class=”msg”>[^<]{1,480}</span> 這種寫法的思路 要好於<span class=”msg”>.*</span> ,原因有二:一是使用[^<] ,它保證了文本的範圍不會超出下一個小於號所在的位置;二是明確長度範圍,{1,480} ,其依據是一條twitter消息大致能的字符長度範圍。當然,480這個長度是否正確還可推敲,但是這種思路是值得借鑑的。說得狠一點,“濫用點號、星號和加號是不環保、不負責任的做法”。
  5. 不要讓稻草壓死駱駝 。每使用一個普通括號()而不是非捕獲型括號(?:…) ,就會保留一部分內存等着你再次訪問。這樣的正則表達式、無限次地運行次數,無異於一根根稻草的堆加,終於能將駱駝壓死。養成合理使用(?:…)括號的習慣。
  6. 寧簡勿繁 。將一條複雜的正則表達式拆分爲兩條或多條簡單的正則表達式,編程難度會降低,運行效率會提升。例如用來消除行首和行尾空白字符的正則表達式s/^/s+|/s+$//g; ,其運行效率理論上要低於s/^/s+//g; s//s+$//g; 。這個例子出自《精通正則表達式》第五章,書中對它的評論是“它幾乎總是最快的,而且顯然最容易理解”。既快又容易理解,何樂而不爲?工作中我們還有其它的理由要將C==(A|B) 這 樣的正則表達式拆爲A和B兩條表達式分別執行。例如,雖然A和B這兩種情況只要有一種能夠擊中所需要的文本模式就會成功匹配,但是如果只要有一條子表達式 (例如A)會產生誤匹配,那麼不論其它的子表達式(例如B)效率如何之高,範圍如何精準,C的總體精準度也會因A而受到影響。
  7. 巧妙定位 。有時候,我們需要匹配的the,是作爲單詞的the(兩邊有空格),而不是作爲單詞一部分的t-h-e的有序排列(例如togethe r中的the)。在適當的時候用上^$/b 等等定位錨點,能有效提升找到成功匹配、淘汰不成功匹配的效率。

總結完發現,《精通正則表達式》的第5章、第6章已經以更爲有條理的方式總結出了常用的優化方法。不過,泛泛地讀過的印象是膚淺的,過後即忘的;而真正若有所悟時在書上得到了系統地印證,這種感覺纔是真的爽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章