1、正則表達式基本使用
測試demo:
private static void test2() {
String regular = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$";
String testStr = "a_行政村行政村選擇充血出現在出effg+現在重置線程這需求行政村行政村選擇充血自產自銷區+";
Pattern p = Pattern.compile(regular);
Matcher m = p.matcher(testStr);
boolean res = m.find();
System.out.println(res);
}
2、正則表達式三種算法
Java 語言使用的正則表達式執行引擎是 NFA (Non-deterministic finite automaton) 非確定型有窮自動機,這種引擎的特點是:功能強大、單存在回溯機制導致執行效率慢(回溯嚴重時可以導致機器 CPU 使用率 100%,直接卡死機器)。
測試代碼:
private static void test1() {
String testStr = "a_行政村行政村選擇充血出現在出effg現在重置線程這需求行政村行政村選擇充血自產自銷區";
String regular_1 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$"; //貪婪模式
String regular_2 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]++$"; //懶惰模式
String regular_3 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+?$"; //獨占模式
List<String> regulars = new ArrayList<>();
regulars.add(regular_1);
regulars.add(regular_2);
regulars.add(regular_3);
for (String regular : regulars) {
long start, end;
start = System.currentTimeMillis();
Pattern p = Pattern.compile(regular);
Matcher m = p.matcher(testStr);
boolean res = m.find();
end = System.currentTimeMillis();
System.out.println("結果:" + JSON.toJSONString(res) + ",執行時間:" + (end - start) + "(ms)");
}
}
執行結果:
結果:true,執行時間:3(ms)
結果:true,執行時間:1(ms)
結果:true,執行時間:0(ms)
可以明顯看到,雖然實現了相同的匹配功能,但效率卻有所區別,原因在于這三種寫法定義了正則表達式的三種匹配邏輯,我們來逐一說明:
正則表達式:ef{1,3}g
待匹配的字符串:effg
2.1 貪婪模式(默認)
語法:ef{1,3}g
原理:貪婪模式是正則表達式的默認匹配方式,在該模式下,對于涉及數量的表達式,正則表達式會盡量匹配更多的內容。
說明:我用模型圖來演示一下匹配邏輯:
到第二步的時候其實已經滿足第二個條件f{1,3},但我們說過貪婪模式會盡量匹配更多的內容,所以依然停在第二個條件繼續遍歷字符串
注意看第四步,字符g不滿足匹配條件f{1,3},這個時候會觸發回溯機制:指針重新回到第三個字符f處
關于回溯機制:
回溯是造成正則表達式效率問題的根本原因,每次匹配失敗,都需要將之前比對過的數據復位且指針調回到數據的上一位置,想要優化正則表達式的匹配效率,減少回溯是關鍵。
回溯之后,繼續從下一個條件以及下一個字符繼續匹配,直到結束。
2.2 懶惰模式
語法: ef{1,3}?g
原理:與貪婪模式相反,懶惰模式會盡量匹配更少的內容。
說明:
到第二步的時候,懶惰模式會認為已經滿足條件f{1,3},所以會直接判斷下一條件。
注意,到這步因為不滿足匹配條件,所以觸發回溯機制,將判斷條件回調到上一個。
回溯之后,繼續從下一個條件以及下一個字符繼續匹配,直到結束。
2.3 獨占模式(推薦)
語法:ef{1,3}+g
原理:獨占模式應該算是貪婪模式的一種變種,它同樣會盡量匹配更多的內容,區別在于在匹配失敗的情況下不會觸發回溯機制,而是繼續向后判斷,所以該模式效率最佳。
說明:
2.4 三種模式表達式
貪婪模式 | 懶惰模式 | 獨占模式 |
---|---|---|
X? | X?? | X?+ |
X* | X*? | X*+ |
X+ | X+? | X++ |
X{n} | X{n}? | X{n}+ |
X{n,} | X{n,}? | X{n,}+ |
X{n,m} | X{n,m}? | X{n,m}+ |
3 優化建議
建議1:推薦使用獨占模式來優化正則比表達式,格式^[允許字符集]+ 。
建議2:推薦將Pattern 緩存下來,避免反復編譯Pattern。
static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");
建議3:優化正則中的分支選擇
通過上面對正則表達式匹配邏輯的了解,我們不難想到,由于回溯機制的存在,帶有分支選擇的正則表達式必然會降低匹配效率
String testStr = "abbdfg";
String regular = "(aab|aba|abb)dfg";
在這個例子中,"aab"并未匹配,于是回溯到字符串的第一個元素重新匹配第二個分支"aba",以此類推,直到判斷完所有分支,效率問題可想而知。
如果分支中存在公共前綴,可以進行提取:
String regular = "a(ab|ba|bb)dfg";
這樣首先減少了公共前綴的判斷次數,其次降低了分支造成的回溯頻率,相比之下效率有所提升。