読者です 読者をやめる 読者になる 読者になる

感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

Javaでファイルが作成されるのを待って、作成を検知してから次の処理を行う

Java SE
<スポンサーリンク>

Javaでファイルが作成されたことを検知して、そのファイルが作成された後に次の処理を開始したい、なんてことがあるかもしれない。
たとえば、外部からファイルが連携されるプロセスがあって、そのファイルができるかどうかをずーーーっと待って、ファイルができたら、ファイルを読み込む処理を始めるとか。

あるいは、時間がかかるバッチ処理があって、バッチ処理が終わったら「end.txt」みたいなのができて、その「end.txt」ができたら次の処理に進んでみたりとか。

今回は、あるディレクトリに「end.txt」ができるのをじっと監視して、そこにend.txtができたら、メインの処理を再開する、みたいな処理を作ってみる。
コードはサンプルで、なんか汚いのでリファクタリングの余地はたくさんあるが、まぁとりあえず動いているものを載せてみる。

まず、あるディレクトリの動きをじっと監視するコードだ。

WatchServiceの使い方の一例として見ていただければ。

package watch;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class Watcher implements Runnable {

	private String watchPath;
	private String watchTextName;

	public String getWatchPath() {
		return watchPath;
	}

	public void setWatchPath(String watchPath) {
		this.watchPath = watchPath;
	}

	public String getWatchTextName() {
		return watchTextName;
	}

	public void setWatchTextName(String watchTextName) {
		this.watchTextName = watchTextName;
	}

	@Override
	public void run() {
		Path dir = Paths.get(watchPath);

		WatchService watcher;
		try {
			watcher = FileSystems.getDefault().newWatchService();
			dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);

			while (true) {
				WatchKey watchKey = watcher.take();

				Path name = null;
				for (WatchEvent<?> event : watchKey.pollEvents()) {
					WatchEvent.Kind<?> kind = event.kind();

					//A special event to indicate that events may have been lost or discarded.
					if (kind == StandardWatchEventKinds.OVERFLOW) {
						continue;
					}

					name = (Path) event.context();
					Path child = dir.resolve(name);


					if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
						System.out.println(child + "が作成されました!");
						System.out.println("できたファイルの名前は・・・" + name.toString());

						if (name.toString().equals(watchTextName)) {
							System.out.println("ほしいテキストができたから、ループを抜けるよ・・・");

							//ここでforを抜ける
							break;
						}
						watchKey.reset();
					}
				}

				//forを抜けたら、今度はwhileを抜けないと。でもこの辺のコード重複しててなんか汚い
				if (name != null && name.toString().equals(watchTextName)) {
					break;
				}

			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}


そいで、このWatchServiceを別スレッドとして走らせる。
このスレッドはじっとディレクトリを監視するわけだ。

で、Thread#joinを使って、そのスレッドが終わるまでじっと待つようにするのがコレ。

package action;

import watch.Watcher;


public class Main {
	public static void main(String[] args) throws InterruptedException {

		System.out.println("メイン処理を開始します!!");

		Watcher watcher = new Watcher();

		watcher.setWatchPath("C:\\tmp\\");
		watcher.setWatchTextName("end.txt");

		Thread thread = new Thread(watcher);

		thread.start();

		thread.join();

		System.out.println("ent.txtができたので、メイン処理を再開します!");

	}
}


コンソールには以下のように表示される。

メイン処理を開始します!!
C:\tmp\end.txtが作成されました!
できたファイルの名前は・・・end.txt
ほしいテキストができたから、ループを抜けるよ・・・
ent.txtができたので、メイン処理を再開します!

コマンドプロンプトで空ファイルを作成する以下のようなコマンドを実行すると、

C:\tmp>type nul > end.txt
C:\tmp\end.txtが作成されました!
できたファイルの名前は・・・end.txt
ほしいテキストができたから、ループを抜けるよ・・・

と出て、メインの処理が再開される。

ここまで書いて思ったんだけど、監視スレッドを終わらせるんじゃなくて、ずっと走らせておいてもいい気がしてきた。

というのも、mainの処理の方をwhile(true)ループで待機させておいて、監視が見つかったら、boolean existsなんかにtrueを設定するようにする。

if (exists) {
	
}

end.txtができたらbreakするようにすればいいんだ。


<参考>

Java逆引きレシピ

Java逆引きレシピ

この本はめっちゃいい。
最近買ったんだけど、手元に置いておくとすげー便利。
正直、今となっては知ってることもたくさんあるんだけど、
コードの書き方って全部は記憶できてないじゃん。
で、いちいちググるんだけど、それよりも早いし、正確。

ググって出てくる情報よりも、新しいしね。
これは、Java開発の際に手元に置いておく"べき"と強く言える良い本だった。