Javaでの正規表現のパフォーマンスの概要
1. 概要
このクイックチュートリアルでは、パターンマッチングエンジンがどのように機能するかを示します。 また、Javaでregular expressionsを最適化するさまざまな方法を紹介します。
regular expressionsの使用の概要については、to this article hereを参照してください。
2. パターンマッチングエンジン
java.util.regexパッケージは、Nondeterministic Finite Automaton (NFA). It’s considered nondeterministic because while trying to match a regular expression on a given string, each character in the input might be checked several times against different parts of the regular expression.と呼ばれるタイプのパターンマッチングエンジンを使用します
バックグラウンドでは、上記のエンジンはbacktrackingを使用します。 この一般的なアルゴリズムは、失敗を宣言するまですべての可能性を使い果たしようとします。 NFAをよりよく理解するために、次の例を検討してください。
"tra(vel|ce|de)m"
入力String「travel」を使用すると、エンジンは最初に「tra」を探し、すぐに見つけます。
その後、4番目の文字から「vel」を一致させようとします。 これは一致するため、先に進んで「m」と一致しようとします。
それは一致しません。そのため、4番目の文字に戻り、「ce」を検索します。 繰り返しますが、これは一致しないため、再び4番目の位置に戻り、「de」を試してみます。 その文字列も一致しないため、入力文字列の2番目の文字に戻り、別の「tra」を検索しようとします。
最後の失敗では、アルゴリズムは失敗を返します。
最後の簡単な例では、入力Stringを正規表現に一致させようとしているときに、エンジンが数回バックトラックする必要がありました。 そのため、it’s important to minimize the amount of backtracking that it does.
3. Regular Expressionsを最適化する方法
3.1. 再コンパイルを避ける
Javaの正規表現は、内部データ構造にコンパイルされます。 このコンパイルは時間のかかるプロセスです。
String.matches(String regex) メソッドを呼び出すたびに、指定された正規表現が再コンパイルされます。
if (input.matches(regexPattern)) {
// do something
}
ご覧のとおり、条件が評価されるたびに、正規表現がコンパイルされます。
最適化するには、最初にパターンをコンパイルしてから、Matcherを作成して、値の一致を見つけることができます。
Pattern pattern = Pattern.compile(regexPattern);
for(String value : values) {
Matcher matcher = pattern.matcher(value);
if (matcher.matches()) {
// do something
}
}
上記の最適化の代わりに、同じMatcherインスタンスをreset()メソッドで使用することもできます。
Pattern pattern = Pattern.compile(regexPattern);
Matcher matcher = pattern.matcher("");
for(String value : values) {
matcher.reset(value);
if (matcher.matches()) {
// do something
}
}
Matcherはスレッドセーフではないため、このバリエーションの使用には注意が必要です。 マルチスレッドのシナリオでは、危険である可能性があります。
要約すると、ある時点でMatcherのユーザーが1人だけであることが確実なすべての状況で、resetで再利用しても問題ありません。 残りの部分については、プリコンパイル済みを再利用するだけで十分です。
3.2. 交替の使用
前のセクションで確認したように、交互の不適切な使用はパフォーマンスに害を及ぼす可能性があります。 より早く一致させることができるように、発生する可能性が高いオプションを前面に配置することが重要です。
また、それらの間の共通パターンを抽出する必要があります。 置くのは同じではありません:
(travel | trade | trace)
より:
tra(vel | de | ce)
後者の方が高速です。これは、NFAが「tra」と一致しようとし、それが見つからない場合は代替案を試行しないためです。
3.3. グループのキャプチャ
グループをキャプチャするたびに、短時間のペナルティが発生します。
グループ内のテキストをキャプチャする必要がない場合は、キャプチャしないグループの使用を検討する必要があります。 「(M)」の代わりに「(?:M)」を使用してください。
4. 結論
この簡単な記事では、NFAがどのように機能するかを簡単に再検討しました。 次に、パターンを事前にコンパイルしてMatcherを再利用することにより、正規表現のパフォーマンスを最適化する方法の調査に進みました。
最後に、交代やグループで作業する際に留意すべきいくつかの考慮事項を指摘しました。
いつものように、完全なソースコードはover on GitHubにあります。