正規表現によるマッチング

正規表現 (Regular Expression)

正規表現とは?

正規表現 (regular expression)とは、文字列の集合(パターン)を表す汎用的な記法です。 例えば、文字列「book または cook」のことを正規表現で [bc]ook と書くことができます。 [bc] は 「b か c かどちらか 1 文字」という意味です。 文字列「h1~h6 のいずれか」を表すには h[1-6] と書きます。

正規表現などで表される文字列パターンに合致する文字列を探すことを、 パターンマッチと言います。 Java、Perl、Ruby、Python などの言語は正規表現を扱うことができるため、 パターンマッチを行うプログラムを容易に作成することができます。

正規表現は Java が生まれる前からあるものです。 興味のある人は、UNIX のコマンド grep、sed、awk を調べてみましょう。

正規表現の記法

記法については、Java における正規表現のクラス java.util.regex.Pattern の説明文を参照してください。 ここではよく使う記法について例を挙げておきます。

文字

正規表現の例意味マッチする例
通常の文字そのままの文字「abc」なら「abc」
.任意の1文字「a」でも「b」でも何でも
\n改行
\tタブ
\. \( \) \[ \] \^ \$ \\記号ではなく文字「.」「(」「)」「[」「]」「^」「$」「\」

「\」はエスケープ文字です。本当はバックスラッシュ「\」ですが、日本語環境では円マーク「¥」が表示されます。

Java のソースコード内の文字列定数(""内)では、 ただ「\」と書くと文字列定数としての特殊記号として扱われてしまうため、 そもそも文字「\」を「\\」と書く必要があります。 よって、正規表現としての「\.」は「\\.」、「\\」は「\\\\」などと書く必要があります。

文字クラス

正規表現の例意味マッチする例
[abc]abcのうち1文字「a」「b」「c」のいずれか
[^abc]abc以外の1文字「d」など
[A-Za-z]AからZ、aからzのうちの1文字「d」など
\d数字1文字「0」から「9」までのいずれか
\w英数字1文字「a」「B」「7」など
\s空白文字1文字空白、改行、タブなど
\p{InBasicLatin}ラテン文字1文字「a」「B」記号など
\p{InHiragana}平仮名1文字「あ」「ん」など
\p{InKatakana}片仮名1文字「ア」「ン」など
\p{InCJKUnifiedIdeographs}漢字1文字「漢」「字」など

上記の \p{...} は Unicodeブロック と呼ばれるものです。

境界

正規表現の例意味マッチする例
^行頭「^un」は「un」で始まる文字列
$行末「ing$」は「ing」で終わる文字列

数量子

正規表現の例意味マッチする例
?直前の文字が0個か1個「e?grep」は「grep」か「egrep」
*直前の文字が0個以上「.*」は 0文字以上の任意の文字列
+直前の文字が1個以上「.+」は 1文字以上の任意の文字列
\{n}直前の文字がn個「a\{2}」は「aa」
\{n,}直前の文字がn個以上「a\{2,}」は「aa」「aaa」...
\{n,m}直前の文字がn個以上,m個以下「a\{2,3}」は「aa」「aaa」
+?直前の文字が1個以上,ただしマッチする最小の範囲

論理演算子

正規表現の例意味備考
tdu|dendaitdu または dendai範囲を区切るため全体を()で囲むことが多い
()グループ分けgroup(n)でn番目のグループにマッチした文字列を参照できる

Javaにおける正規表現

Javaには正規表現に関する機能を実現するためのパッケージ java.util.regex があります (Java SDK 1.4 で導入)。 このパッケージにより、強力な正規表現の機能を持つ Perl とほぼ同等の機能が実現されています。

クラス Pattern のメソッド

static Pattern compile(String regex)
指定された正規表現 regex をパターンにコンパイルし、Pattern クラスのインスタンスとして返す。
static Pattern compile(String regex, int flags)
指定されたフラグを使用して、指定された正規表現 regex をパターンにコンパイルし、Pattern クラスのインスタンスとして返す。フラグの例: Pattern.DOTALL を使用すると . が改行にもマッチするようになる。文字列の途中に改行を含み、複数行にわたってマッチさせる場合に有効。
Matcher matcher(CharSequence input)
指定された入力 input と、このパターンとをマッチする正規表現エンジンを作成する。 String や StringBuffer クラスは CharSequence インタフェースを実装しているので、引数として与えることができる
static boolean matches(String regex, CharSequence input)
指定された正規表現をコンパイルして、指定された入力とその正規表現をマッチする。
String[] split(CharSequence input)
このパターンを区切り文字列として、指定された入力シーケンスを分割する。

クラス Matcher のメソッド

boolean find()
入力シーケンスからこのパターンとマッチする次の部分シーケンスを検索する。
String group()
前回のマッチで一致した入力部分シーケンスを返す。 引数のある group と異なり、マッチした部分シーケンス全体を返す
String group(int group)
前回のマッチで一致した入力部分シーケンスがある。その中で、group 番目のグループにマッチした部分を返す。 なお、グループとはパターン内で()で表現されるくくりのことで、 先頭から順に 1,2,3...と番号付けされる(0からではない)
boolean lookingAt()
入力シーケンスの先頭からパターンとマッチする。
boolean matches()
入力シーケンス全体をこのパターンとマッチする。
String replaceAll(String replacement)
パターンとマッチする入力シーケンスの部分シーケンスを、指定された置換文字列に置き換える。
String replaceFirst(String replacement)
パターンとマッチする入力シーケンスの部分シーケンスのうち、最初の部分シーケンスを指定された置換文字列に置き換える。
Matcher reset(CharSequence input)
新しい入力シーケンスを使用してこの正規表現エンジンをリセットする。

テスト用プログラム

キーボードからまず正規表現のパターンを入力し、 つぎに任意の文字列を入力してマッチするか確認することができるプログラムを示します。

import java.util.*;
import java.util.regex.*;

class PatternTester {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("テストする正規表現を入力してください。");
        Pattern pattern = Pattern.compile(scanner.nextLine());

        System.out.println("マッチさせる文字列を入力してください(Ctrl-dで終了)。");
        while (scanner.hasNext()) {     // Ctrl-d が来ない限り繰り返す
            // 1行分の文字列を読み込む
            String nextLine = scanner.nextLine();
            // nextLine に対してパターンマッチをする Matcher インスタンスを生成
            Matcher matcher = pattern.matcher(nextLine);
            // 先頭から探せる限り探す
            while (matcher.find()) {
                System.out.println("マッチしました: " + matcher.group());
            }
        }
    }
}

PatternTester.java

なお、Scanner クラスはそれ自体が正規表現に対応しているため、 Matcher を明示的に使わない書き方もできます。

        System.out.println("マッチさせるテキストを入力してください(Ctrl-dで入力終了)。");
        while(true) {
            String string = scanner.findInLine(pattern);
            if(string == null)
                break;
            else
                System.out.println("マッチしました: " + string);
        }

コード例 - HTMLのタグ除去

Webページ(HTML文書)などで使われるタグを除去します。 正規表現でタグをマッチさせ、replaceAll メソッドでマッチした部分を空文字列に置き換えます。

1つのタグがマッチする正規表現を考えます。 <.+?> という正規表現で、< > に囲まれた1文字以上の文字列を表します。 なお、.+? は最短のマッチを表します。 ちょっと考えると .+ (任意の文字の1文字以上の連続)でよさそうに思えますが、 そうすると以下のような場合、

<em>content</em>

.+ がマッチする範囲は「 em>content</em 」となってしまいます。

import java.io.*;
import java.net.*;
import java.util.regex.*;

public class TagRemover {
    public static void main(String[] args) {
        try {
            // ネットワーク接続の準備
            URL url = new URL("http://web.dendai.ac.jp/");
            URLConnection connection = url.openConnection();
            connection.connect();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));

            // 1行ごとにタグ除去
            Pattern pattern = Pattern.compile("<.+?>");
            String line;
            while ((line = reader.readLine()) != null) {
                Matcher matcher = pattern.matcher(line);
                String plainText = matcher.replaceAll("");
                if(plainText.length() != 0) {        // 空文字列はスキップ
                    System.out.println(plainText);
                }
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TagRemover.java

コード例 - リンク先の抽出

正規表現では () でグループを表し、 その部分にマッチした文字列を参照することができます。 グループには先頭から 1,2,... と番号がつけられていますので、 group(番号n) で n番目のグループにマッチした文字列が得られます。

import java.util.regex.*;

class LinkFinder {
    public static void main(String args[]) {
        Pattern pattern = Pattern.compile("href=\"(.+?)\"");  // グループの指定が1つ(1番目)
        Matcher matcher = pattern.matcher("<a href=\"index.html\">トップへ</a>");
        if(matcher.find()) {
            System.out.println(matcher.group(1));   // 1番目のグループにマッチした文字列を取り出す
        }
    }
}

LinkFinder.java

String の正規表現関連メソッド

文字列の置換と分割については、 String クラスから直接行えるメソッドが用意されています。

String replaceAll(String regex, String replacement)
指定された正規表現に一致する、この文字列の各部分文字列に対し、指定された置換を実行する。
String replaceFirst(String regex, String replacement)
指定された正規表現に一致する、この文字列の最初の部分文字列に対し、指定された置換を実行する。
String[] split(String regex)
この文字列を、指定された正規表現に一致する位置で分割する。