ファイル変更通知と呼ばれるこの機能を実装するには、ファイル・システム上の関連ディレクトリに対する操作をプログラムで検出できる必要があります。 方法の1つに、ファイル・システムのポーリングによる変更の検出がありますが、このアプローチは非効率的です。 監視が必要なオープン中のファイルやディレクトリが数百にも上るような、規模の大きいアプリケーションには対応できません。
java.nio.file
パッケージには、WatchService
APIというファイル変更通知用のAPIが用意されています。 このAPIでは、ディレクトリを監視サービスに登録できます。 登録時に、監視するイベント・タイプをサービスに指定します。 イベント・タイプには、ファイル作成、ファイル削除、ファイル変更があります。 指定したイベントがサービスによって検出されると、このイベントは、登録済みプロセスに転送されます。 登録済みプロセスには、登録対象のイベントが起きていないか監視するための専用スレッド(またはスレッド・プール)があります。 転送されたイベントは、必要に応じて処理されます。
このセクションでは次の内容について説明します。
WatchService
APIは高度なものではありませんが、カスタマイズできます。 そのまま使用することも、ここで説明するメカニズムに基づいて非常に高度なAPIを作成し、特定のニーズに対応することもできます。
監視サービスを実装するためには、次のような基本的な手順が必要です。
WatchService
を"watcher"という名前で作成します。
WatchKey
インスタンス(監視キー)を取得します。
closed
メソッドの呼出し時)に終了します。
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
のイベント・タイプは次のとおりです。
ENTRY_CREATE
– ディレクトリ・エントリの作成。
ENTRY_DELETE
– ディレクトリ・エントリの削除。
ENTRY_MODIFY
– ディレクトリ・エントリの変更。
OVERFLOW
– イベントが消失したか破棄された可能性があることを示す特殊なイベント・タイプ。 このOVERFLOW
イベントは、通知を受けるために登録する必要はありません。
次のコードでは、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); }
イベント処理ループでの処理手順は、次のとおりです。
poll
– キューに配置された監視キーを取得できる場合は、その監視キーを返します。 取得できない場合は、即座にnull
値を返します。
poll(long, TimeUnit)
– キューに配置された監視キーを取得できる場合は、その監視キーを返します。 キューに配置された監視キーを即座に取得できない場合は、指定した時間待機します。 TimeUnit
引数には、ナノ秒、ミリ秒など、指定した時間の単位を指定します。
take
– キューに配置された監視キーを返します。 キューに配置された監視キーを取得できない場合は、待機します。
pollEvents
メソッドを使用して、WatchEvent
が格納されたList
を取得します。
kind
メソッドを使用して、イベント・タイプを取得します。 監視キーがどのイベント・タイプに対して登録されているかに関係なく、OVERFLOW
イベントが取得される可能性があります。 このイベントは、処理することも無視することもできますが、イベントのテストは行ってください。
context
メソッドを使用します。
reset
を呼び出して、監視キーのステータスをReady
に戻す必要があります。 このメソッドがfalse
を返す場合は、監視キーは有効ではなく、ループを終了できます。 この手順は非常に重要です。 reset
を呼び出さなければ、この監視キーではそれ以降のイベントは取得されません。
監視キーにはその状態を示すステータスがあります。 監視キーは常に、次のいずれかのステータスとなります。
Ready
は、監視キーでイベントを取得する準備ができていることを示します。 監視キーは最初に作成された時点では、Ready
ステータスになります。
Signaled
は、1つ以上のイベントがキューに配置されていることを示します。 監視キーがSignaled
ステータスになると、reset
メソッドが呼び出されるまではReady
ステータスに戻りません。
Invalid
は、監視キーが有効ではないことを示します。 次のいずれかの場合にこのステータスとなります。
次に、イベント処理ループのサンプル・コードを見てみましょう。 これはサンプル・プログラム
の一部です。このコードはディレクトリを監視し、新しいファイルが作成されるまで待機します。 新しいファイルを取得できるようになると、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>
にキャストしている行です。 サンプルの
では次のように、チェックしていない(unchecked)処理に関する警告を非表示にするWatchDir
cast
ユーティリティ・メソッドを作成して、このエラーを回避しています。
@SuppressWarnings("unchecked") static <T> WatchEvent<T> cast(WatchEvent<?> event) { return (WatchEvent<Path>)event; }
@SuppressWarnings
構文について詳しくない場合は、Annotationsを参照してください。
監視サービスAPIは、ファイル変更イベントに関して通知する必要のあるアプリケーションを対象に設計されています。 エディタやIDEなどの、多数のファイルがオープンされている可能性があり、ファイルがファイル・システムと同期している必要のあるアプリケーションに適しています。 また、ディレクトリを監視し、デプロイする.jsp
ファイルや.jar
ファイルが格納されることを待機しているようなアプリケーション・サーバーにも適しています。
このAPIは、ハード・ドライブのインデックス作成の目的では設計されていません。 ほとんどのファイル・システム実装では、ファイルの変更通知機能がネイティブ・サポートされています。 監視サービスAPIは、可能な場合は、このサポートを活用します。 ただし、ファイル・システムでこのメカニズムがサポートされない場合は、監視サービスはファイル・システムをポーリングしてイベントを待機します。