
ファイル変更通知と呼ばれるこの機能を実装するには、ファイル・システム上の関連ディレクトリに対する操作をプログラムで検出できる必要があります。 方法の1つに、ファイル・システムのポーリングによる変更の検出がありますが、このアプローチは非効率的です。 監視が必要なオープン中のファイルやディレクトリが数百にも上るような、規模の大きいアプリケーションには対応できません。
java.nio.fileパッケージには、WatchService APIというファイル変更通知用のAPIが用意されています。 このAPIでは、ディレクトリを監視サービスに登録できます。 登録時に、監視するイベント・タイプをサービスに指定します。 イベント・タイプには、ファイル作成、ファイル削除、ファイル変更があります。 指定したイベントがサービスによって検出されると、このイベントは、登録済みプロセスに転送されます。 登録済みプロセスには、登録対象のイベントが起きていないか監視するための専用スレッド(またはスレッド・プール)があります。 転送されたイベントは、必要に応じて処理されます。
このセクションでは次の内容について説明します。
WatchService APIは高度なものではありませんが、カスタマイズできます。 そのまま使用することも、ここで説明するメカニズムに基づいて非常に高度なAPIを作成し、特定のニーズに対応することもできます。
監視サービスを実装するためには、次のような基本的な手順が必要です。
WatchServiceを"watcher"という名前で作成します。
WatchKeyインスタンス(監視キー)を取得します。
closedメソッドの呼出し時)に終了します。
WatchKeyはスレッド・セーフであり、java.nio.concurrentパッケージとともに使用できます。 この処理にはスレッド・プールを使用できます。
この手順で作成するAPIは比較的高度なものであるため、作業を進める前に試行してください。 サンプル・プログラムのをコンピュータに保存し、コンパイルします。 このプログラムに渡すWatchDirtestディレクトリを作成します。 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は、監視キーが有効ではないことを示します。 次のいずれかの場合にこのステータスとなります。
次に、イベント処理ループのサンプル・コードを見てみましょう。 これはサンプル・プログラムの一部です。このコードはディレクトリを監視し、新しいファイルが作成されるまで待機します。 新しいファイルを取得できるようになると、EmailprobeContentType(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)処理に関する警告を非表示にするWatchDircastユーティリティ・メソッドを作成して、このエラーを回避しています。
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<Path>)event;
}
@SuppressWarnings構文について詳しくない場合は、Annotationsを参照してください。
監視サービスAPIは、ファイル変更イベントに関して通知する必要のあるアプリケーションを対象に設計されています。 エディタやIDEなどの、多数のファイルがオープンされている可能性があり、ファイルがファイル・システムと同期している必要のあるアプリケーションに適しています。 また、ディレクトリを監視し、デプロイする.jspファイルや.jarファイルが格納されることを待機しているようなアプリケーション・サーバーにも適しています。
このAPIは、ハード・ドライブのインデックス作成の目的では設計されていません。 ほとんどのファイル・システム実装では、ファイルの変更通知機能がネイティブ・サポートされています。 監視サービスAPIは、可能な場合は、このサポートを活用します。 ただし、ファイル・システムでこのメカニズムがサポートされない場合は、監視サービスはファイル・システムをポーリングしてイベントを待機します。