Java 正則引擎_正則優化_grok

1、正則引擎

Java使用的正則引擎是NFA(非確定型有限狀態自動機)
一個狀態可以通過標記了
字符ε的多條邊跳轉到另一個狀態。字符或ε 分別表示讀入一個字符或不讀入字符可以跳轉到另一個狀態上,比如遇到表達式的右括號和回溯。
NFA的特點是匹配過程面臨很多的岔路做出選擇,一旦某條岔路失敗,就需要回溯,類似於深度優先搜索。不過並不一定完全遍歷,完成匹配之後就停止搜索了。
比如(a|b)*abb

NFA引擎是表達式主導的,依次嘗試正則子表達式的各種可能特點是匹配同一個字符串,不同的表達式會影響引擎做出不同的執行過程
控制權在表達式上流動。
匹配了控制權就在表達式上向右移到下一個匹配組,比如一個括號裏的內容或者元字符。
NFA的狀態可以理解爲“字符串當前匹配位置和控制權在表達式的位置”。
比如用a(
c|b)匹配abc的過程:
1)一開始控制權在表達式開頭的a,匹配字符串的a,匹配成功;
2)控制權後移到c,去匹配字符串的
b,匹配失敗;
3)控制權右移到b
,去匹配字符串的b,匹配成功,結束;
ac|ab匹配
abc的過程會在以上過程2)和3)之間多一個回溯:
控制權右移到
aba上,字符串回溯到a,再匹配,匹配成功;

還有一種正則引擎是DFA引擎(確定型有限狀態自動機),文本主導,根據文本去淘汰正則的子表達式
只有一條字母標記的轉換狀態的邊,意味着讀入字符時只有一種可能去跳轉狀態。
可以理解爲一個NFA上的很多可能可以轉換成很多個DFA。
優點:每個字母只匹配一次,效率高
缺點:太確定了,不如
NFA回溯提供的靈活

兩條不同正則引擎都普適的原則:
1)
最左優先匹配
匹配先從需要查找的字符串的起始位置嘗試匹配;在當前位置測試整個正則表達式能匹配的每個可能,匹配到達表達式末尾就成功,否則回溯去測試其他可能。
2)標準的量詞(
\*+?{m,n})匹配優先的(貪婪模式)

2、正則優化

依據:只要引擎報告匹配失敗,它就必然嘗試了所有可能。所以,如果有太多的回溯的可能,那麼可能會導致程序阻塞,優化正則表達式的一個重要部分就是最小化回溯的數量
1、測試:因處理匹配失敗需要儘可能的回溯,所以較複雜的正則可以試着使用其不能匹配的字符串去測試,檢查耗時漏洞。
2、選擇:ac|aba(c|b)提取共用模式,依據最左優先匹配將常用的選擇項放在前面。
3、
閉包
避免.* +,用{m,n}來規定;
避免貪婪模式,使用懶惰模式,如{0,10}?

考慮使用佔有優先量詞
?+ *+ ++ {m,n}+,先儘可能多地去匹配,表達式後面不匹配時,也不會交還字符去回溯;如.*[0-9]*是減少正則表達式所需要的猜測
避免*
包含正則後面的內容,不然引起不必要的回溯,如.*[0-9]*
4使用邊界匹配器/錨點^$\b等),限定指定位置
5、
考慮使用環視結構零寬斷言
(?<=Expression)逆序肯定環視,前綴是Expression
(?<!Expression)逆序否定環視,前綴不是Expression
(?=Expression)順序肯定環視,後綴是
Expression
(?!Expression)順序否定環視,後綴不是Expression

逆序是從在字符串上當前迭代位置向左去匹配,順序是向右;Java只支持逆序環視長度確定的表達式;可以圈定匹配範圍,匹配失敗不會進行回溯,而是進入下一輪迭代。
6用最小範圍的元字符, 少用 . 字符,儘量避免用過大的元字符,比如不用([\w\-]+)匹配字母單詞,\w是包括字母數字下劃線的
7、根據優先級不去使用冗餘的括號。
NFA的三種基本運算:聯合運算
r|s連接運算 rs閉包Kleene運算 r*
優先級:閉包運算符優先級最高,且是左結合的,連接第二,聯合運算優先級最低。
8、區分捕獲組()和非捕獲組(?:),需要提取的用捕獲組,不需的用非捕獲組,減少狀態記錄,提高效率。

Pattern pattern = Pattern.compile("(\\d+)(?:\\.?)(?:\\d+)([¥$])$");
        Matcher matcher = pattern.matcher("8899.56¥");

        if (matcher.find()) {
            System.out.println(matcher.group(0));
            System.out.println(matcher.group(1));
            System.out.println(matcher.group(2));
        }

3、Java使用grok

Grok 最主要是Logstash 的一個重要插件。
Logstash 是可以動態地採集、轉換和傳輸服務器端數據的一個管道,並且不受格式或複雜度的影響。爲何可以不受格式的影響,就是因爲它的Grok插件。
通常用來把單行非結構的日誌數據,通過它默認內置的很多預定義的正則表達式,去進行逐步的模式匹配,轉換成結構化的日誌。
grok的pattern文件裏預定義好的一些正則匹配:

語法:%{PATTERN_NAME:NAMED_GROUP_IN_RESULT}
比如一條Apache的日誌:localhost GET /index.html 1024 0.016


Java裏使用grok:
可以在Maven庫裏看到有非常多的grok,一開始都不知道該用哪個: https://mvnrepository.com/search?q=grok

<!-- https://mvnrepository.com/artifact/io.thekraken/grok -->
<dependency>
    <groupId>io.thekraken</groupId>
    <artifactId>grok</artifactId>
    <version>0.1.5</version>
</dependency>

Last Release on Nov 15, 2016

<!-- https://mvnrepository.com/artifact/io.krakens/java-grok -->
<dependency>
    <groupId>io.krakens</groupId>
    <artifactId>java-grok</artifactId>
    <version>0.1.9</version>
</dependency>

(May 08, 2018)

io.thekraken的grok是io.krakens的舊版本,使用方法可參考大佬博客:https://blog.csdn.net/HarderXin/article/details/76838914 還有很多其他家的grok,相應官網上都有使用方法: https://github.com/aicer/grok http://grok.nflabs.com/HowToUse
最正統的logstash上的grok插件: https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns

1)添加resources/pattern.txt,按照grok的pattern文件裏預定義匹配式的格式,在文件里加入自定義的匹配式
2)引入
Maven

<dependency>
      <groupId>io.krakens</groupId>
      <artifactId>java-grok</artifactId>
      <version>0.1.9</version>
    </dependency>

3)使用Grok相關類處理

private static final String GROK_PATTERN_PATH = "src/main/resources/pattern.txt";
    private static InputStream inputStream = null;
    public static void main( String[] args )
    {
        try {
            //創建實例
            inputStream = new FileInputStream(new File(GROK_PATTERN_PATH));
            GrokCompiler grokCompiler = GrokCompiler.newInstance();
            grokCompiler.register(inputStream);

            //設置匹配式
            final Grok grok1 = grokCompiler1.compile("%{IPORHOST:client} %{WORD:method} %{URIPATHPARAM:request} %{INT:size} %{NUMBER:duration}");
            String log1 = "localhost GET /index.html 1024 0.016";
            Match gm1 = grok1.match(log1);
            
            //執行匹配
            final Map<String, Object> capture1 = gm1.capture();
            System.out.println(capture1);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("讀取pattern文件失敗");
                }
            }
        }

在線調試:http://grokdebug.herokuapp.com/
官方:https://github.com/thekrakken/java-grok

4)處理結果

{duration=0.016, request=/index.html, method=GET, size=1024, URIPATH=/index.html, client=localhost, URIPARAM=null}

grok評估:

1最大的優勢是它預定義很多針對日誌內容的匹配式但規則抽取還是需要比較多自定義的匹配式,沒有利用到
2、
抽取結果可以自動形成鍵值對,如果能在處理函數中獲取,那麼過濾操作時不用重新拿正則去匹配;
3、
正則對比最致命的是,如果一行包含多個可匹配結果,grok第一次匹配到就返回了,不會繼續向前匹配,如

String log1 = "112 169 19 192";
final Grok grok1 = grokCompiler.compile("%{INT}");
//結果:{INT=112}
final Grok grok1 = grokCompiler.compile("(%{INT} )+");
//結果:{INT=19},使用+ ? * {n},只取最後一個匹配的
final Grok grok1 = grokCompiler.compile("%{INT} %{INT}");
//結果:{INT=[112, 169]}

意味着使用一條規則能在句子裏匹配到兩個及以上的話,正則可以用while (matcher.find())都獲取到;而grok會返回某一個

 

 

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