改進(jìn)正則表達(dá)式的性能

正則表達(dá)式的應(yīng)用原理

正則表達(dá)式應(yīng)用到目標(biāo)字符串的過程大致分為下面幾步:

  • 編譯正則表達(dá)式。檢查正則表達(dá)式的正確性窗轩,如果正確夯秃,將其編譯為內(nèi)部形式座咆。
  • 開始傳動(dòng)痢艺。將正則引擎定位到目標(biāo)字符串的起始位置。
  • 檢測(cè)元素介陶。
  1. 引擎開始依次測(cè)試表達(dá)式的各個(gè)元素和目標(biāo)字符串堤舒。
  2. 在檢測(cè)相連元素時(shí),引擎會(huì)在某個(gè)元素匹配失敗時(shí)停止哺呜。
  3. 量詞修飾的元素舌缤,控制權(quán)在量詞和被限定的元素之間輪換。

控制權(quán)在捕獲型括號(hào)內(nèi)外切換會(huì)帶來額外的開銷某残。捕獲型括號(hào)內(nèi)表達(dá)式匹配的文本必須保留国撵,才能通過$1\1來引用。括號(hào)也可能屬于某個(gè)回溯分支玻墅,括號(hào)內(nèi)的狀態(tài)就是用于回溯的狀態(tài)的一部分介牙,所以進(jìn)入和退出捕獲型括號(hào)時(shí)需要修改狀態(tài)。

  • 尋找匹配結(jié)果澳厢。若找到匹配結(jié)果环础,傳統(tǒng)型NFA立即報(bào)告匹配成功(POSIX NFA則在嘗試完所有可能的情況之后,返回最長(zhǎng)的匹配)剩拢。若沒有找到匹配线得,從當(dāng)前傳動(dòng)位置前進(jìn)一位,開始下一輪嘗試徐伐。
  • 匹配徹底失敗贯钩。在所有可能的嘗試都失敗之后,報(bào)告失敗办素。

全面考察回溯

下面我們通過幾個(gè)例子來考察回溯在匹配成功和不成功時(shí)的各種細(xì)節(jié)角雷。如表達(dá)式".*"對(duì)字符串The name "McDonald's" is said "makudonarudo" in Japanese的匹配過程如下:

<small>圖1</small>

我們知道".*"!無法匹配上述字符串,但引擎在報(bào)告匹配失敗之前仍會(huì)進(jìn)行多次嘗試:
<small>圖2</small>

如果我們將.換成[^&quot;]摸屠,[^&quot;]*匹配的內(nèi)容就能不包括雙引號(hào)谓罗,這減少了匹配和回溯的次數(shù):
<small>圖3</small>

但是需要注意的是"[^&quot;]*"".*"在此例中的匹配結(jié)果并不一樣。

一個(gè)簡(jiǎn)單的例子

假設(shè)字符串"2 \"x3\" likeness"季二,為了匹配雙引號(hào)及之內(nèi)的字符串檩咱,且允許出現(xiàn)轉(zhuǎn)義的雙引號(hào)揭措。"(\\\\.|[^&#92;&#92;&quot;])*"的匹配結(jié)果雖然正確,但在效率方面有所欠缺刻蚯,通過優(yōu)化能加快匹配速度绊含。

調(diào)整多選結(jié)構(gòu)的順序

對(duì)于一般的雙引號(hào)字符串而言,普通字符的數(shù)量要比轉(zhuǎn)義字符多炊汹,將[^&#92;&#92;&quot;]放到\\\\.之前可以有效減少回溯次數(shù)躬充。

<small>圖4</small>

調(diào)整分支的順序必須要保證排序與匹配成功無關(guān)。同時(shí)讨便,這種改動(dòng)并不能加快報(bào)告失敗的速度充甚,因?yàn)樵趫?bào)告匹配失敗之前,所有可能的匹配都已經(jīng)被嘗試霸褒。

目標(biāo)字符串 "(\\.|[^\\"])*" "([^\\"]|\\.)*"
"2"x3" likeness" 32次測(cè)試伴找,14次回溯 22次測(cè)試,4次回溯
"... 99 more chars ..." 218次測(cè)試废菱,109次回溯 111次測(cè)試技矮,2次回溯
"no "match" here 124次測(cè)試,86次回溯 124次測(cè)試殊轴,86次回溯

消除循環(huán)

由于*控制著捕獲型括號(hào)內(nèi)的多選結(jié)構(gòu)衰倦,每次進(jìn)出括號(hào)都意味著狀態(tài)的切換,為避免這部分的消耗旁理,可以通過消除循環(huán)的技巧對(duì)表達(dá)式進(jìn)行改進(jìn)樊零。這項(xiàng)技巧我們將在下文講到,這里給出當(dāng)前例子在消除循環(huán)之后的表達(dá)式是"[^&#92;&#92;&quot;]+(\\\\.[^&#92;&#92;&quot;]+)*"韧拒。

錯(cuò)誤的優(yōu)化

為了減少*的迭代次數(shù)淹接,在[^&#92;&#92;&quot;]后引入+。對(duì)于不存在轉(zhuǎn)義字符的字符串而言叛溢,這樣會(huì)一次性讀入整個(gè)字符串塑悼,而不用進(jìn)行回溯。這改動(dòng)似乎帶來了不錯(cuò)的收益楷掉,在匹配成功的時(shí)候也確是如此厢蒜。但在匹配失敗時(shí),卻會(huì)造成指數(shù)級(jí)的回溯烹植。例如目標(biāo)字符串是makudonarudo斑鸦,+會(huì)對(duì)字符串做任意長(zhǎng)度的切割,*再在切割的基礎(chǔ)上進(jìn)行多次迭代草雕。長(zhǎng)度為n的字符串巷屿,回溯的次數(shù)是$2^{n+1}$,獨(dú)立測(cè)試的次數(shù)為$2^{n+1}+2^n$墩虹。如"2\"x3\" likeness and makudonarudo這種長(zhǎng)度的目標(biāo)字符串時(shí)就會(huì)造成應(yīng)用程序的未響應(yīng)嘱巾。

常見的優(yōu)化措施

對(duì)于正則引擎憨琳,各流派有自己的實(shí)現(xiàn)和優(yōu)化措施。實(shí)現(xiàn)方案互有差異旬昭,優(yōu)化措施也不盡相同篙螟,但通常可以歸納為兩類:

  • 加速某些操作问拘。
  • 避免冗余操作遍略。如果引擎認(rèn)為某些操作對(duì)于產(chǎn)生正確的結(jié)果是不必要的,或者某些操作能夠應(yīng)用到比之前更少的文本骤坐,而忽略這些操作可以節(jié)省時(shí)間绪杏。比如以^$錨定位置的表達(dá)式只有在行首(尾)才能匹配或油,若匹配失敗寞忿,引擎不會(huì)在其它位置進(jìn)行無謂的嘗試。

對(duì)某個(gè)正則表達(dá)式的改動(dòng)顶岸,在某個(gè)流派的實(shí)現(xiàn)方式中可能帶來收益,而在另一個(gè)實(shí)現(xiàn)方式中卻與期望背道而馳叫编。在進(jìn)行優(yōu)化時(shí)辖佣,檢測(cè)并性能測(cè)試實(shí)際期望應(yīng)用的同類型數(shù)據(jù),總是有助于判斷改動(dòng)是否有益搓逾。

應(yīng)用正則表達(dá)式之前的優(yōu)化措施

用戶通過Pattern.compile在正則表達(dá)式應(yīng)用之前卷谈,完成對(duì)正則表達(dá)式的編譯。尤其是循環(huán)之前編譯正則表達(dá)式霞篡,可以有效減少構(gòu)建表達(dá)式內(nèi)部形式的次數(shù)世蔗。這種方式,被稱為“面向?qū)ο笫教幚碇械木幾g緩存” 朗兵。此外還有“集成式處理中的編譯緩存” 和“程序式處理中的編譯緩存”污淋,此處就不做介紹了。

通過傳動(dòng)裝置進(jìn)行優(yōu)化

行錨點(diǎn)優(yōu)化余掖。能使用這種優(yōu)化措施的引擎知道寸爆,在錨定位置才能滿足表達(dá)式的匹配條件,傳動(dòng)裝置會(huì)直接略過目標(biāo)字符串中的其它位置的字符盐欺。

優(yōu)化正則表達(dá)式本身

  • 消除不必要的括號(hào)赁豆。對(duì)于不需要捕獲的分組,用(?: expression)代替(expression)冗美。
  • 消除不必要的字符組魔种。沒必要對(duì)單個(gè)字符應(yīng)用字符組,特別是[.]粉洼,完全可以用\.來代替节预。
  • 避免指數(shù)級(jí)的回溯甲抖。對(duì)于(.+)*之類的量詞結(jié)合結(jié)構(gòu),+*二者分隔目標(biāo)字符串為任意長(zhǎng)度的子字符串心铃,制造出指數(shù)級(jí)的回溯准谚。
  • 使用占有優(yōu)先量詞/固化分組削減狀態(tài)。在確定量詞限定的元素與其之后的元素不會(huì)匹配重疊的情況下去扣,可以使用占有優(yōu)先量詞柱衔,或者固化分組減少存儲(chǔ)的備用狀態(tài)。當(dāng)然這種做法必須建立在引擎支持占有優(yōu)先量詞愉棱,或者固化分組的基礎(chǔ)上唆铐。
  • 用字符組代替多選結(jié)構(gòu)。[uvwxyz]代替u|v|w|x|y|z奔滑,因?yàn)榍罢咧贿M(jìn)行匹配操作艾岂,而后者可能需要在目標(biāo)字符串的每個(gè)位置進(jìn)行6次回溯。由這個(gè)例子可以看出多選結(jié)構(gòu)或許是造成回溯的主要原因朋其。
  • 控制多選結(jié)構(gòu)的順序王浴。將最可能匹配的分支放在前面,減少回溯梅猿。
  • 將多選結(jié)構(gòu)中開頭相同的字符提取到多選結(jié)構(gòu)之前氓辣。this|that就可以改成th(?:is|at)。這樣th只需檢查一遍袱蚓,只有在確實(shí)需要的時(shí)候才會(huì)用到代價(jià)相對(duì)高昂的多選結(jié)構(gòu)功能钞啸。
  • 按實(shí)際情況選擇忽略優(yōu)先量詞和匹配優(yōu)先量詞。忽略優(yōu)先量詞通常要比匹配優(yōu)先量詞要慢(這不是言之確鑿的)喇潘。另外對(duì)大多數(shù)引擎而言体斩,排除型字符組的效率要比忽略優(yōu)先量詞快的多。如"[^&quot;]*"".*?"颖低。
  • 避免過于復(fù)雜的正則表達(dá)式絮吵。

消除循環(huán)

所謂“循環(huán)”,指得是(this|that|...)*這類表達(dá)式中*代表的意義枫甲。消除循環(huán)這項(xiàng)技巧對(duì)于某些常用的表達(dá)式來說源武,加速效果非常明顯。消除循環(huán)常用的解法是:

opening normal* (special normal*)* closing

這種解法中最重要的是要區(qū)分表達(dá)式中的specialnormal部分想幻。一般而言粱栖,對(duì)于目標(biāo)字符串中最常見的部分用normal子表達(dá)式處理,用special子表達(dá)式作為檢查點(diǎn)處理其余不常見的部分脏毯。為避免(special normal*)*中的無休止匹配(即指數(shù)型回溯)闹究,需要確保以下三點(diǎn):

  • special部分和normal部分匹配的開頭不能重合;
  • normal部分必須能匹配至少一個(gè)字符食店;
  • special部分是固化的渣淤。當(dāng)存在多種方式匹配同樣的文本時(shí)赏寇,(special normal*)*最終也難免淪為(expression*)*,也就無從避免無休止匹配了价认。

例1:<B>(?>[^&lt;]*)(?>(?!</?B>)<[^&lt;]*)*</B>代替<B>((?!</?B>).)*</B>嗅定,這里的固化分組不是必須的,但如果只能部分匹配用踩,使用固化分組可以提高速度渠退。
例2:/\*[^&#42;]*\*+([^&#47;&#42;][^&#42;]*\*+)*/匹配java文件中的多行注釋。
回過頭來看消除循環(huán)這項(xiàng)技巧脐彩,它對(duì)表達(dá)式的可讀性和可維護(hù)性造成了一定影響碎乃,但是測(cè)試證明其帶來的速度收益也同樣十分明顯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惠奸,一起剝皮案震驚了整個(gè)濱河市梅誓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佛南,老刑警劉巖梗掰,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異共虑,居然都是意外死亡愧怜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門妈拌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蓬蝶,你說我怎么就攤上這事尘分。” “怎么了丸氛?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵培愁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缓窜,道長(zhǎng)定续,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任禾锤,我火速辦了婚禮私股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恩掷。我一直安慰自己倡鲸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布黄娘。 她就那樣靜靜地躺著峭状,像睡著了一般克滴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上优床,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天劝赔,我揣著相機(jī)與錄音,去河邊找鬼胆敞。 笑死着帽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竿秆。 我是一名探鬼主播启摄,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼幽钢!你這毒婦竟也來了歉备?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤匪燕,失蹤者是張志新(化名)和其女友劉穎蕾羊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帽驯,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡龟再,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尼变。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利凑。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嫌术,靈堂內(nèi)的尸體忽然破棺而出哀澈,到底是詐尸還是另有隱情,我是刑警寧澤度气,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布割按,位于F島的核電站,受9級(jí)特大地震影響磷籍,放射性物質(zhì)發(fā)生泄漏适荣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一院领、第九天 我趴在偏房一處隱蔽的房頂上張望弛矛。 院中可真熱鬧,春花似錦栅盲、人聲如沸汪诉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扒寄。三九已至鱼鼓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間该编,已是汗流浹背迄本。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留课竣,地道東北人嘉赎。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像于樟,于是被迫代替她去往敵國(guó)和親公条。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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