このページを正しく表示するには、ブラウザでJavaScriptを有効にする必要があります。
コース: 重要なクラス
レッスン: 基本的なI/O
セクション: ファイルI/O(NIO.2を含む)
ディレクトリの変更監視
ホームページ > 重要なクラス > 基本的なI/O

ディレクトリの変更監視

IDEなどのエディタを使用してファイルを編集しているときに、ダイアログ・ボックスが表示されて、オープン中のファイルがファイル・システム上で変更されたためリロードする必要があると通知された経験はないでしょうか。 あるいはNetBeans IDEなどのアプリケーションで、通知もなくファイルが更新されたことがあるかもしれません。 たとえばフリー・エディタのjEditでは、変更の検出を通知するために下のようなダイアログ・ボックスが表示されます。

Sample jEdit Dialog stating: The following files were changed on disk by another program.

ファイルの変更が検出されたことを通知するjEditダイアログ・ボックス

ファイル変更通知と呼ばれるこの機能を実装するには、ファイル・システム上の関連ディレクトリに対する操作をプログラムで検出できる必要があります。 方法の1つに、ファイル・システムのポーリングによる変更の検出がありますが、このアプローチは非効率的です。 監視が必要なオープン中のファイルやディレクトリが数百にも上るような、規模の大きいアプリケーションには対応できません。

java.nio.fileパッケージには、WatchService APIというファイル変更通知用のAPIが用意されています。 このAPIでは、ディレクトリを監視サービスに登録できます。 登録時に、監視するイベント・タイプをサービスに指定します。 イベント・タイプには、ファイル作成、ファイル削除、ファイル変更があります。 指定したイベントがサービスによって検出されると、このイベントは、登録済みプロセスに転送されます。 登録済みプロセスには、登録対象のイベントが起きていないか監視するための専用スレッド(またはスレッド・プール)があります。 転送されたイベントは、必要に応じて処理されます。

このセクションでは次の内容について説明します。

監視サービスの概要

WatchService APIは高度なものではありませんが、カスタマイズできます。 そのまま使用することも、ここで説明するメカニズムに基づいて非常に高度なAPIを作成し、特定のニーズに対応することもできます。

監視サービスを実装するためには、次のような基本的な手順が必要です。

WatchKeyはスレッド・セーフであり、java.nio.concurrentパッケージとともに使用できます。 この処理にはスレッド・プールを使用できます。

監視サービスの試行

この手順で作成するAPIは比較的高度なものであるため、作業を進める前に試行してください。 サンプル・プログラムのWatchDirをコンピュータに保存し、コンパイルします。 このプログラムに渡すtestディレクトリを作成します。 WatchDirではすべてのイベントの処理に1つのスレッドが使用されるため、イベントの待機中はキーボード入力がブロックされます。 別のウィンドウでプログラムを実行するか、次のようにバックグラウンドで実行します。

java WatchDir test &

testディレクトリでファイルの作成、削除、編集を試行します。 これらのイベントが発生すると、メッセージがコンソールに出力されます。 完了後は、testディレクトリを削除すると、WatchDirが終了します。 または、プロセスを手動でkillすることもできます。

-rオプションを指定してファイル・ツリー全体を監視することも可能です。 -rを指定すると、WatchDirではファイル・ツリーを探索しながら、監視サービスに各ディレクトリを登録します。

監視サービスの作成とイベントの登録

最初の手順として、次のようにFileSystemクラスのnewWatchServiceメソッドを使用して、新しいWatchServiceを作成します。

WatchService watcher = FileSystems.getDefault().newWatchService();

次に、作成した監視サービスに、1つ以上のオブジェクトを登録します。 Watchableインタフェースを実装するオブジェクトであれば何でも登録できます。 PathクラスはWatchableインタフェースを実装しているので、監視対象の各ディレクトリはPathオブジェクトとして登録します。

他のあらゆるWatchableクラスと同様に、Pathクラスは2つのregisterメソッドを実装しています。 このページでは引数が2つのバージョンのregister(WatchService, WatchEvent.Kind<?>...)を使用します。 (引数が3つのバージョンではWatchEvent.Modifierを受け入れますが、これは現時点では実装されていません。)

オブジェクトを監視サービスに登録する際に、監視対象のイベント・タイプを指定します。 サポートされるStandardWatchEventKindのイベント・タイプは次のとおりです。

次のコードでは、3つのイベント・タイプすべてに対してPathインスタンスを登録しています。

import static java.nio.file.StandardWatchEventKind.*;

Path dir = ...;
try {
    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException x) {
    System.err.println(x);
}

イベントの処理

イベント処理ループでの処理手順は、次のとおりです。

  1. 監視キーを取得します。 次の3つのメソッドが用意されています。
    • poll – キューに配置された監視キーを取得できる場合は、その監視キーを返します。 取得できない場合は、即座にnull値を返します。
    • poll(long, TimeUnit) – キューに配置された監視キーを取得できる場合は、その監視キーを返します。 キューに配置された監視キーを即座に取得できない場合は、指定した時間待機します。 TimeUnit引数には、ナノ秒、ミリ秒など、指定した時間の単位を指定します。
    • take – キューに配置された監視キーを返します。 キューに配置された監視キーを取得できない場合は、待機します。
  2. 監視キーの保留中イベントを処理します。 pollEventsメソッドを使用して、WatchEventが格納されたListを取得します。
  3. kindメソッドを使用して、イベント・タイプを取得します。 監視キーがどのイベント・タイプに対して登録されているかに関係なく、OVERFLOWイベントが取得される可能性があります。 このイベントは、処理することも無視することもできますが、イベントのテストは行ってください。
  4. イベントに関連付けられたファイル名を取得します。 ファイル名はイベントのコンテキストとして保存されます。そのため、ファイル名を取得するにはcontextメソッドを使用します。
  5. 監視キーのイベントを処理後、resetを呼び出して、監視キーのステータスをReadyに戻す必要があります。 このメソッドがfalseを返す場合は、監視キーは有効ではなく、ループを終了できます。 この手順は非常に重要です。 resetを呼び出さなければ、この監視キーではそれ以降のイベントは取得されません。

監視キーにはその状態を示すステータスがあります。 監視キーは常に、次のいずれかのステータスとなります。

次に、イベント処理ループのサンプル・コードを見てみましょう。 これはサンプル・プログラムEmailの一部です。このコードはディレクトリを監視し、新しいファイルが作成されるまで待機します。 新しいファイルを取得できるようになると、probeContentType(Path)メソッドを使用して、それがtext/plainファイルであるかを確認します。 この目的は、text/plainファイルを電子メールでエイリアスに送信することですが、その実装の詳細についてはこのサンプルでは省略されています。必要に応じて処理を追加してください。

監視サービスAPIに固有のメソッドについては、太字で示しています。

for (;;) {

    //監視キーの送信を待機
    WatchKey key;
    try {
	key = watcher.take();
    } catch (InterruptedException x) {
	return;
    }

    for (WatchEvent<?> event: key.pollEvents()) {
	WatchEvent.Kind<?> kind = event.kind();

        //この監視キーが登録されるイベントはENTRY_CREATEイベントだけですが、
        //イベントが消失したり破棄されたりした場合は、OVERFLOWイベントが
        //発生することがあります。
	if (kind == OVERFLOW) {
	    continue;
	}

        //ファイル名はイベントのコンテキストです。
	WatchEvent<Path> ev = (WatchEvent<Path>)event;
	Path filename = ev.context();

	//新しいファイルがテキスト・ファイルであることを確認します。
	try {
            //ファイル名のディレクトリを解決します。
            //ファイル名が"test"、ディレクトリが"foo"の場合、
            //解決後の名前は"test/foo"となります。
            Path child = dir.resolve(filename);
	    if (!Files.probeContentType(child).equals("text/plain")) {
		System.err.format("New file '%s' is not a plain text file.%n", filename);
		continue;
	    }
	} catch (IOException x) {
	    System.err.println(x);
	    continue;
	}

	//ファイルを指定した電子メール・エイリアスに送信します。
	System.out.format("Emailing file %s%n", filename);
	//送信処理の詳細は省略しています。
    }

    //監視キーをリセットします。この手順は、この後さらに監視イベントを取得する場合は
    //非常に重要です。 監視キーが有効ではない場合は、ディレクトリに
    //アクセスできないため、ループを終了します。
    boolean valid = key.reset();
    if (!valid) {
        break;
    }
}

ファイル名の取得

ファイル名は、イベント・コンテキストから取得します。 サンプルのEmailでは、次のコードでファイル名を取得しています。

WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();

Emailをコンパイルすると、次のようなエラーが発生します。

Note: Email.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

このエラーの原因となっているコードは、WatchEvent<T>WatchEvent<Path>にキャストしている行です。 サンプルのWatchDirでは次のように、チェックしていない(unchecked)処理に関する警告を非表示にするcastユーティリティ・メソッドを作成して、このエラーを回避しています。

@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
    return (WatchEvent<Path>)event;
}
@SuppressWarnings構文について詳しくない場合は、Annotationsを参照してください。

このAPIを使用する状況と使用しない状況

監視サービスAPIは、ファイル変更イベントに関して通知する必要のあるアプリケーションを対象に設計されています。 エディタやIDEなどの、多数のファイルがオープンされている可能性があり、ファイルがファイル・システムと同期している必要のあるアプリケーションに適しています。 また、ディレクトリを監視し、デプロイする.jspファイルや.jarファイルが格納されることを待機しているようなアプリケーション・サーバーにも適しています。

このAPIは、ハード・ドライブのインデックス作成の目的では設計されていません。 ほとんどのファイル・システム実装では、ファイルの変更通知機能がネイティブ・サポートされています。 監視サービスAPIは、可能な場合は、このサポートを活用します。 ただし、ファイル・システムでこのメカニズムがサポートされない場合は、監視サービスはファイル・システムをポーリングしてイベントを待機します。


サンプル・プログラムで問題が発生した場合は、 Compiling and Running the Examples: FAQsを参照してください。
フィードバックをお寄せください。さまざまなご意見をお待ちしております。

前のページ: ファイルの検索
次のページ: その他の便利なメソッド