パフォーマンスに優れた正規表現の記述

ここでは、パフォーマンスに優れた正規表現を作成する際に考慮する必要がある重要な側面について説明します。

パーサー、ラベルおよびデータフィルタで使用できる正規表現構文については、Java Platform Standard Ed. 8 Documentationを参照してください。

参照および問合せ時正規表現の場合は、RE2J構文(Java Implementation of RE2)を参照してください。

文字クラス

文字クラスでは、一致させる文字または一致させない文字を指定します。.*s.をより具体的な文字に置き換えてください。.*は、常に入力の末尾までシフトし、その後、逆に戻ります。つまり、前に保存された状態に戻って一致の検索が継続されます。特定の文字クラスを使用すると、*によって正規表現エンジンが処理する文字数を制御し、無制限な逆戻りを停止できます。

次の正規表現の例を考えます:

(\d{4})(\d{2})(\d{2}),(\S+),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+)

次の入力について:

20150220,201502,16798186260,tvN,Entertainment/Music,Female,67,2,Individual,ollehtv Economic,Commercial Housing,5587,0,2,0,1,1

指定した正規表現の結果として、一致は逆戻りに陥る可能性があります。この状況はOracle Logging Analyticsによって検出され、一致操作は中断されます。

正規表現を次の例に変更すると、一致を高速に完了できます。[\S\s]*[^,]に変更され、不要な逆戻りが回避されていることに注意してください。

(\d{4})(\d{2})(\d{2}),(\S+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+)

最短一致量指定子

多くの正規表現で、最長一致量指定子(.*s)を最短一致量指定子(.*?s)に置き換えると、結果を変えることなく安全に正規表現のパフォーマンスを上げることができます。

次の入力について考えます:

Trace file /u01/app/oracle/diag/rdbms/navisdb/NAVISDB/trace/NAVISDB_arc0_3941.trc
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
ORACLE_HOME = /u01/app/oracle/product/11.2.0/db_1
System name: Linux
Node name: NAVISDB
Release: 2.6.18-308.el5
Version: #1 SMP Fri Jan 27 17:17:51 EST 2012
Machine: x86_64
Instance name: NAVISDB
Redo thread mounted by this instance: 1
Oracle process number: 21
Unix process pid: 3941, image: oracle@NAVISDB (ARC0)

指定された入力について、次の最長一致の正規表現を考えます:

Trace\sfile\s(\S*).*ORACLE_HOME\s*[:=]\s*(\S*).*System\sname:\s*(\S*).*Node\sname:\s*(\S*).*Release:\s*(\S*).*Machine:\s*(\S*).*Instance\sname:\s*(\S*).*Redo\sthread\smounted\sby\sthis\sinstance:\s(\d*).*Oracle\sprocess\snumber:\s*(\d*).*Unix\sprocess\spid:\s(\d*).*image:\s+([^\n\r]*)

正規表現エンジンは、.*.を検出するたびに入力の末尾まで向かいます。最初に.*が出現すると、すべての入力が処理されて、その後、ORACLE_HOMEに達するまで逆に戻ります。これは、非効率的な一致方法です。かわりとなる最短一致の正規表現は次のとおりです:

Trace\sfileRelease:\s*(\S*).*?Machine:\s*(\S*).*?Instance\sname:\s*(\S*).*?Redo\sthread\smounted\sby\sthis\sinstance:\s(\d*).*?Oracle\sprocess\snumber:\s*(\d*).*?Unix\sprocess\spid:\s(\d*).*?image:\s+([^\n\r]*)

前述の最短一致の正規表現は、文字列の先頭から開始してORACLE_HOMEに達するまで処理し、その時点で残りの文字列の一致に進むことができます。

ノート: ORACLE_HOMEフィールドが入力の最初の方に出現する場合、最短一致量指定子を使用する必要があります。ORACLE_HOMEフィールドが最後の方に出現する場合は、最長一致量指定子を使用する方が適切なことがあります。

アンカー

アンカーは、カーソルを入力内の特定の場所に配置することを正規表現エンジンに指示します。最も一般的なアンカーは、入力の先頭と末尾を示す^および$です。

IPv4アドレスを検出するための次の正規表現を考えます:

\d{1,3}\.d{1,3}.\d{1,3}.\d{1,3}
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

2番目の正規表現は^で始まっており、入力の先頭に出現するIPアドレスを具体的に示していることに注意してください。

次の例のような入力で正規表現を検索します:

107.21.20.1 - - [07/Dec/2012:18:55:53 -0500] "GET
      /extension/bsupport/design/cl/images/btn_letschat.png HTTP/1.1" 200
    2144

一致しない入力は、次の例のようになります:

[07/Dec/2012:23:57:13 +0000] 1354924633 GET "/favicon.ico" "" HTTP/1.1 200 82726 "-"
      "ELB-HealthChecker/1.0"

^で始まる2番目の正規表現は、一致しない入力でより高速に実行されます(一致しない入力はすぐに破棄されるためです)。

先後指定の重要性

先後指定の順序は重要であるため、より一般的なオプションを先に配置して、より高速に一致するようにします。一般的でないオプションを先に配置すると、正規表現エンジンは、一致する可能性の高いより一般的なオプションをチェックする前にそれらをチェックするため、時間が無駄になります。同様に、共通パターンを抽出するようにします。たとえば、(abcd|abef)のかわりにab(cd|ef)を使用します。

次の正規表現を確認してください:

{TIMEDATE}\s{0,1}:\s*(?:|\[)(\w+)(?:\:|\])(?:\[|)(\d+)(?:\:|\])(.{1,1000}).*
{TIMEDATE}\s{0,1}:\s*(?:\[|)(\w+)(?:\:|\])(?:\[|)(\d+)(?:\:|\])(.{1,1000}).*

次の入力で:

2014-06-16 12:13:46.743: [UiServer][1166092608] {0:7:2} Done for
ctx=0x2aaab45d8330

2番目の正規表現は、先後指定によって先に文字[が、次にnullが検索されるため、より高速に一致します。入力に[があるため、一致はより高速に実行されます。

サンプルの解析式

次のサンプルの解析式を参照して、ログ・ファイルから値を抽出するための適切な解析式を作成できます。

ログ・ファイルは、複数のフィールド値の連結により生成されたエントリで構成されます。特定のフォーマットのログ・ファイルの分析では、すべてのフィールド値を表示する必要がないこともあります。パーサーを使用すると、表示する必要があるフィールドのみから値を抽出できます。

パーサーは、定義された解析式に基づいてログ・ファイルからフィールドを抽出します。解析式は、検索パターンを定義する正規表現の形式で記述します。解析式では、ログ・エントリから抽出する一致フィールドごとに、検索パターンをカッコ()で囲みます。カッコの外側の検索パターンと一致する値は抽出されません。

例1

次のサンプル・ログ・エントリを解析する場合:

Jun 20 15:19:29 hostabc rpc.gssd[2239]: ERROR: can't open clnt5aa9: No such file or directory
Jul 29 11:26:28 hostabc kernel: FS-Cache: Loaded
Jul 29 11:26:28 hostxyz kernel: FS-Cache: Netfs 'nfs' registered for caching

必要な解析式は次のとおりです:

(\S+)\s+(\d+)\s(\d+):(\d+):(\d+)\s(\S+)\s(?:([^:\[]+)(?:\[(\d+)\])?:\s+)?(.+)

前述の例で、解析式によって取得される値の一部は、次のとおりです:

  • (\S+): 月に対応する空白以外の複数の文字

  • (\d+): 日に対応する空白以外の複数の文字

  • (?:([^:\[]+): (オプション) ^、:、\、[]以外のすべての文字。これはサービス名用です

  • (.+): (オプション)主要なメッセージ・コンテンツ

例2

次のサンプル・ログ・エントリを解析する場合:

####<Apr 27, 2014 4:01:42 AM PDT> <Info> <EJB> <host> <AdminServer> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <OracleSystemUser> <BEA1-13E2AD6CAC583057A4BD> <b3c34d62475d5b0b:6e1e6d7b:143df86ae85:-8000-000000000000cac6> <1398596502577> <BEA-010227> <EJB Exception occurred during invocation from home or business: weblogic.ejb.container.internal.StatelessEJBHomeImpl@2f9ea244 threw exception: javax.ejb.EJBException: what do i do: seems an odd quirk of the EJB spec. The exception is:java.lang.StackOverflowError>
####<Jul 30, 2014 8:43:48 AM PDT> <Info> <RJVM> <example.com> <> <Thread-9> <> <> <> <1406735028770> <BEA-000570> <Network Configuration for Channel "AdminServer" Listen Address example.com:7002 (SSL) Public Address N/A Http Enabled true Tunneling Enabled false Outbound Enabled false Admin Traffic Enabled true ResolveDNSName Enabled false> 

必要な解析式は次のとおりです:

####<(\p{Upper}\p{Lower}{2})\s+([\d]{1,2}),\s+([\d]{4})\s+([\d]{1,2}):([\d]{2}):([\d]{2})\s+(\p{Upper}{2})(?:\s+(\w+))?>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<\d{10}\d{3}>\s+<(.*?)>\s+<(.*?)(?:\n(.*))?>\s*

前述の例で、解析式によって取得される値の一部は、次のとおりです:

  • (\p{Upper}\p{Lower}{2}): 月に対応する3文字の短縮名。最初の文字は大文字で、2個の小文字が続きます

  • ([\d]{1,2}): 1または2桁の日

  • ([\d]{4}): 4桁の年

  • ([\d]{1,2}): 1または2桁の時間

  • ([\d]{2}): 2桁の分

  • ([\d]{2}): 2桁の秒

  • (\p{Upper}{2}): 2文字のAM/PM (大文字)

  • (?:\s+(\w+)): (オプション。一部のエントリはこれに対して何も値を返さない可能性があります)タイムゾーンに対応する複数の英数字

  • (.*?): (オプション。一部のエントリはこれに対して何も値を返さない可能性があります)重大度レベルに対応する1個以上の文字で、この場合は<INFO>

  • (.*): メッセージに付随する追加の詳細

検索パターン

次の表で、一般的に使用されるいくつかのパターンについて説明します:

パターン 説明
. 改行を除く任意の文字 d.fは、def、daf、dbfなどに一致します
* ゼロ個以上 D*E*F*は、DDEEFF、DEF、DDFF、EEFFなどに一致します
? 1個またはゼロ個(オプション) colou?rは、colourとcolorの両方に一致します
+ 1個以上 Stage \w-\w+は、Stage A-b1_1、Stage B-a2などに一致します
{2} 正確に2個 [\d]{2}は、01、11、21などに一致します
{1,2} 2から4個 [\d]{1,2}は、1、12などに一致します
{3,} 3個以上 [\w]{3,}は、ten、hello、h2134などに一致します
[ … ] 大カッコ内の文字の1つ [AEIOU]は、大文字の母音の1つに一致します
[x-y] xからyまでの範囲内の文字の1つ [A-Z]+は、ACT、ACTION、BATなどに一致します
[^x] xではない1個の文字 [^/d]{2}は、AA、BB、ACなどに一致します
[^x-y] xからyまでの範囲に含まれない文字の1つ [^a-z]{2}は、A1、BB、B2などに一致します
[\d\D] 数字または数字以外の1個の文字 [\d\D]+は、通常のドットでは一致しない改行を含む任意の文字に一致します
\s 空白 (\S+)\s+(\d+)は、AA 123、a_ 221などに一致します
\S 空白ではない1個の文字 (\S+)は、abcd、ABC、A1B2C3などに一致します
\n 改行 (\d)\n(\w)は、次に一致します:

1

A

\w 英数字 [\w-\w\w\w]は、a-123、1–aaaなどに一致します
\p{Lower} 小文字 \p{Lower}{2}は、aa、ab、ac、bbなどに一致します
\p{Upper} 大文字 \p{Upper}は、A、B、Cなどに一致します
?、[]、*、.が後に続く\ \の後の文字をリテラルとして使用するためのエスケープ文字 \?は?を返します