如何寫出高效率的正則表達式
如果純粹是為了挑戰自己的正則水平,用來實現一些特效(例如使用正則表達式計算質數、解線性方程),效率不是問題;如果所寫的正則表達式只是為了滿足一兩次、幾十次的運行,優化與否區別也不太大。但是,如果所寫的正則表達式會百萬次、千萬次地運行,效率就是很大的問題了。泰州網站建設這里(li)總結了幾條提升正則(ze)表達式運行效率的經驗。
為行文方便(bian),先(xian)定義兩個概念(nian)。
誤匹配:指正(zheng)則表(biao)達式所(suo)匹(pi)(pi)配(pei)的(de)(de)(de)內容(rong)范(fan)(fan)圍(wei)超出了所(suo)需要范(fan)(fan)圍(wei),有(you)些文本明(ming)明(ming)不符合要求(qiu),但是被所(suo)寫(xie)的(de)(de)(de)正(zheng)則式“擊中了”。例如,如果使用d{11}來匹(pi)(pi)配(pei)11位(wei)的(de)(de)(de)手(shou)(shou)機號(hao)(hao),d{11}不單能匹(pi)(pi)配(pei)正(zheng)確的(de)(de)(de)手(shou)(shou)機號(hao)(hao)碼,它還會匹(pi)(pi)配(pei)98765432100這(zhe)樣的(de)(de)(de)明(ming)顯(xian)不是手(shou)(shou)機號(hao)(hao)碼的(de)(de)(de)字符串。我們把這(zhe)樣的(de)(de)(de)匹(pi)(pi)配(pei)稱之為(wei)誤(wu)匹(pi)(pi)配(pei)。
漏匹配:指正(zheng)則(ze)表達(da)式所(suo)匹配的(de)(de)(de)內(nei)容所(suo)規(gui)定的(de)(de)(de)范圍太狹窄(zhai),有(you)些文(wen)本確實是(shi)(shi)所(suo)需要的(de)(de)(de),但是(shi)(shi)所(suo)寫(xie)的(de)(de)(de)正(zheng)則(ze)沒有(you)將這(zhe)種情況囊括(kuo)在內(nei)。例如(ru),使用d{18}來匹配18位的(de)(de)(de)身(shen)份證號碼,就會漏掉結(jie)尾是(shi)(shi)字母X的(de)(de)(de)情況。
寫出一條正(zheng)則表達(da)式,既可能只出現(xian)誤(wu)匹(pi)(pi)(pi)配(pei)(pei)(條件寫得極寬(kuan)松,其范圍大于目標(biao)(biao)文本),也(ye)可能只出現(xian)漏匹(pi)(pi)(pi)配(pei)(pei)(只描述了(le)(le)目標(biao)(biao)文本中多種(zhong)(zhong)(zhong)情況種(zhong)(zhong)(zhong)的(de)一種(zhong)(zhong)(zhong)),還可能既有(you)誤(wu)匹(pi)(pi)(pi)配(pei)(pei)又有(you)漏匹(pi)(pi)(pi)配(pei)(pei)。例如,使用w+.com來(lai)匹(pi)(pi)(pi)配(pei)(pei).com結尾的(de)域名(ming)(ming),既會(hui)誤(wu)匹(pi)(pi)(pi)配(pei)(pei)abc_.com這樣(yang)的(de)字串(合(he)(he)法(fa)的(de)域名(ming)(ming)中不含下劃(hua)線(xian),w包含了(le)(le)下劃(hua)線(xian)這種(zhong)(zhong)(zhong)情況),又會(hui)漏掉ab-c.com這樣(yang)的(de)域名(ming)(ming)(合(he)(he)法(fa)域名(ming)(ming)中可以含中劃(hua)線(xian),但(dan)是w不匹(pi)(pi)(pi)配(pei)(pei)中劃(hua)線(xian))。
精準的正則表達式意味著既無誤匹配且無漏匹配。當然,現實中存在這(zhe)樣的(de)(de)情況(kuang):只能(neng)看到有限(xian)數量的(de)(de)文本(ben),根據這(zhe)些文本(ben)寫規(gui)則,但(dan)是這(zhe)些規(gui)則將會用到海(hai)量的(de)(de)文本(ben)中。這(zhe)種(zhong)情況(kuang)下,盡可能(neng)地(di)(如果不是完全地(di))消(xiao)除誤匹配(pei)以及漏匹配(pei),并提(ti)升運行效率,就是我們的(de)(de)目標(biao)。本(ben)文所提(ti)出的(de)(de)經驗(yan),主(zhu)要是針(zhen)對這(zhe)種(zhong)情況(kuang)。
掌握語法細節。正則表達式在各種語言中,其語法大致相同,細節各有千秋。明確所使用語言的正則的語法的細節,是寫出正確、高效正則表達式的基礎。例如,perl中與w等效的匹配范圍是[a-zA-Z0-9_];perl正則式不支持肯定逆序環視中使用可變的重復(variable repetition inside lookbehind,例如(?<=.*)abc),但是.Net語法是支持這一特性的;又如,JavaScript連逆序環視(Lookbehind,如(?<=ab)c)都不支持,而perl和python是支持的。《精通正則表達式》第3章《正則表達式的特性和流派概覽》明確地列出了各大派系正則的異同,這篇文章也簡要地列出了幾種常用語言、工具中正則的比較。對于具體使用者而言,至少應該詳細了解正在使用的那種工作語言里正則的語法細節。
先粗后精,先加后減。使(shi)用(yong)正則表達式(shi)語法(fa)對于(yu)目標(biao)文(wen)本(ben)進行描述和界(jie)(jie)定,可以像畫(hua)素描一樣,先大致勾勒出框架,再逐步(bu)在局步(bu)實現(xian)細節。仍舉(ju)剛才(cai)的(de)(de)(de)手(shou)機號(hao)的(de)(de)(de)例子,先界(jie)(jie)定d{11},總(zong)不(bu)(bu)(bu)(bu)會錯(cuo);再細化(hua)為1[358]d{9},就向(xiang)前邁(mai)了一大步(bu)(至于(yu)第二位是不(bu)(bu)(bu)(bu)是3、5、8,這(zhe)里無(wu)意深究,只(zhi)舉(ju)這(zhe)樣一個例子,說明逐步(bu)細化(hua)的(de)(de)(de)過程)。這(zhe)樣做(zuo)的(de)(de)(de)目的(de)(de)(de)是先消除漏匹(pi)配(剛開(kai)始先盡可能多地(di)匹(pi)配,做(zuo)加法(fa)),然后再一點(dian)一點(dian)地(di)消除誤匹(pi)配(做(zuo)減(jian)法(fa))。這(zhe)樣有先有后,在考慮時才(cai)不(bu)(bu)(bu)(bu)易(yi)出錯(cuo),從而(er)向(xiang)“不(bu)(bu)(bu)(bu)誤不(bu)(bu)(bu)(bu)漏”這(zhe)個目標(biao)邁(mai)進。
留有余地。所能看到的(de)(de)(de)(de)(de)(de)文(wen)(wen)本sample是(shi)有限(xian)的(de)(de)(de)(de)(de)(de),而(er)待(dai)匹配(pei)檢驗的(de)(de)(de)(de)(de)(de)文(wen)(wen)本是(shi)海(hai)量的(de)(de)(de)(de)(de)(de),暫時不可(ke)見的(de)(de)(de)(de)(de)(de)。對(dui)(dui)于這(zhe)樣(yang)的(de)(de)(de)(de)(de)(de)情況(kuang),在寫(xie)正(zheng)則(ze)表(biao)達(da)式(shi)時要跳出(chu)(chu)所能見到的(de)(de)(de)(de)(de)(de)文(wen)(wen)本的(de)(de)(de)(de)(de)(de)圈(quan)子,開(kai)拓思(si)路,作出(chu)(chu)“戰略性(xing)前瞻”。例如,經常(chang)收到這(zhe)樣(yang)的(de)(de)(de)(de)(de)(de)垃(la)圾(ji)短信(xin):“發*票”、“發#漂”。如果要寫(xie)規則(ze)屏蔽這(zhe)樣(yang)煩人的(de)(de)(de)(de)(de)(de)垃(la)圾(ji)短信(xin),不但要能寫(xie)出(chu)(chu)可(ke)以匹配(pei)當前文(wen)(wen)本的(de)(de)(de)(de)(de)(de)正(zheng)則(ze)表(biao)達(da)式(shi)發[*#](?:票|漂),還(huan)要能夠想到發.(?:票|漂|飄)之(zhi)類可(ke)能出(chu)(chu)現(xian)的(de)(de)(de)(de)(de)(de)“變(bian)種(zhong)”。這(zhe)在具(ju)體的(de)(de)(de)(de)(de)(de)領域(yu)或許會有針對(dui)(dui)性(xing)的(de)(de)(de)(de)(de)(de)規則(ze),不多言。這(zhe)樣(yang)做的(de)(de)(de)(de)(de)(de)目的(de)(de)(de)(de)(de)(de)是(shi)消除漏匹配(pei),延(yan)長正(zheng)則(ze)表(biao)達(da)式(shi)的(de)(de)(de)(de)(de)(de)生命周期。
明確。具體說來,就是謹慎用點號這樣的元字符,盡可能不用星號和加號這樣的任意量詞。只要能確定范圍的,例如w,就不要用點號;只要能夠預測重復次數的,就不要用任意量詞。例如,寫析取twitter消息的腳本,假設一條消息的xml正文部分結構是…且正文中無尖括號,那么[^<]{1,480}這種寫法的思路要好于.*,原因有二:一是使用[^<],它保證了文本的范圍不會超出下一個小于號所在的位置;二是明確長度范圍,{1,480},其依據是一條twitter消息大致能的字符長度范圍。當然,480這個長度是否正確還可推敲,但是這種思路是值得借鑒的。說得狠一點,“濫用點號、星號和加號是不環保、不負責任的做法”。
不要讓稻草壓死駱駝。每使用(yong)一個普通括(kuo)號(hao)()而不是非捕(bu)獲型(xing)括(kuo)號(hao)(?:…),就會保留一部(bu)分(fen)內存等(deng)著你再次(ci)訪問(wen)。這樣的正則表(biao)達(da)式、無限次(ci)地運行(xing)次(ci)數,無異(yi)于(yu)(yu)一根根稻草的堆(dui)加,終(zhong)于(yu)(yu)能將(jiang)駱駝(tuo)壓(ya)死。養成(cheng)合理使用(yong)(?:…)括(kuo)號(hao)的習(xi)慣。
寧簡勿繁。將(jiang)一(yi)條(tiao)(tiao)(tiao)復雜的(de)(de)正(zheng)(zheng)則表(biao)達(da)式(shi)(shi)拆分為兩條(tiao)(tiao)(tiao)或多條(tiao)(tiao)(tiao)簡單的(de)(de)正(zheng)(zheng)則表(biao)達(da)式(shi)(shi),編程難度會降(jiang)低,運行(xing)效(xiao)率會提升。例(li)(li)(li)如(ru)用來消除行(xing)首和(he)行(xing)尾空白字符的(de)(de)正(zheng)(zheng)則表(biao)達(da)式(shi)(shi)s/^s+|s+$//g;,其(qi)(qi)運行(xing)效(xiao)率理(li)論(lun)上要(yao)(yao)低于s/^s+//g; s/s+$//g;。這個(ge)例(li)(li)(li)子(zi)出自《精通(tong)正(zheng)(zheng)則表(biao)達(da)式(shi)(shi)》第五章,書中對(dui)它(ta)的(de)(de)評論(lun)是“它(ta)幾(ji)乎(hu)總是最快的(de)(de),而且顯然最容易理(li)解”。既快又容易理(li)解,何(he)樂而不為?工(gong)作中我(wo)們還(huan)有(you)(you)其(qi)(qi)它(ta)的(de)(de)理(li)由要(yao)(yao)將(jiang)C==(A|B)這樣的(de)(de)正(zheng)(zheng)則表(biao)達(da)式(shi)(shi)拆為A和(he)B兩條(tiao)(tiao)(tiao)表(biao)達(da)式(shi)(shi)分別執行(xing)。例(li)(li)(li)如(ru),雖(sui)然A和(he)B這兩種情況只要(yao)(yao)有(you)(you)一(yi)種能夠擊中所(suo)需要(yao)(yao)的(de)(de)文本模式(shi)(shi)就(jiu)會成功匹配,但是如(ru)果(guo)只要(yao)(yao)有(you)(you)一(yi)條(tiao)(tiao)(tiao)子(zi)表(biao)達(da)式(shi)(shi)(例(li)(li)(li)如(ru)A)會產生誤匹配,那么不論(lun)其(qi)(qi)它(ta)的(de)(de)子(zi)表(biao)達(da)式(shi)(shi)(例(li)(li)(li)如(ru)B)效(xiao)率如(ru)何(he)之高,范圍如(ru)何(he)精準,C的(de)(de)總體(ti)精準度也會因A而受到影響。
巧妙定位。有時候(hou),我們需要匹配(pei)的(de)the,是作(zuo)(zuo)為(wei)(wei)單(dan)詞的(de)the(兩邊(bian)有空(kong)格),而不是作(zuo)(zuo)為(wei)(wei)單(dan)詞一部分的(de)t-h-e的(de)有序排列(例如together中的(de)the)。在適當的(de)時候(hou)用(yong)上^,$,b等等定位錨點,能有效(xiao)提升找(zhao)到成功匹配(pei)、淘汰不成功匹配(pei)的(de)效(xiao)率。