Java:ファイル書き込みの進捗を取得する
長時間かかる処理について、何らのフィードバックも無いと誰しも不安になるものだ。本当に動いているのか、それともハングアップしてしまっているのではないかと。
できないと思いこんでいたのだが、考えてみれば可能だったのだ、少々不確かではあるが。
サンプルコード
まずは実際にこれを行うサンプルコードを示す。
INに指定されたファイルをOUTにGZIP圧縮して書き込むものだ。
進捗状況を1秒ごとに表示し、進捗としては0から1の間のdoubleを表示する。1は完了を示す。
本来であれば、結果のファイルサイズが予めわからないのだが、ここでは元のファイルサイズを指定する。
終了時には1はなく、0.2とか0.3とかになるのだろうが、ユーザにとっては動作していることがわかり、
さらに、0.3だったのが、いきなり完了するという特典もついてくる。
import java.io.*;
import java.nio.file.*;
import java.util.zip.*;
public class FileSizeNotifierTest {
private static Path IN = Paths.get("....");
private static Path OUT = Paths.get("...");
public static void main(String[]args) throws Exception {
FileSizeNotifier.executeNotifyProgress(1000, OUT, Files.size(IN),
()-> {
try (OutputStream output = new GZIPOutputStream(Files.newOutputStream(OUT))) {
Files.copy(IN, output);
return (Object)null;
}
}, n-> {
System.out.println("" + n);
});
}
}
FileSizeNotifier
上の例で使ったFileSizeNotifierは以下である。
import java.io.*;
import java.nio.file.*;
import java.util.concurrent.*;
import java.util.function.*;
public class FileSizeNotifier {
/**
* 指定された{@link Callable}を別スレッドで動作させる。このスレッドで、対象ファイルに書き込みがされるものとする。
* この増加するファイルサイズのトータルサイズに対する割合を0から1の間で通知する。
* 通知する間隔としては、{@link interval}に指定されたミリ秒時間になる。
*
* @param interval 通知間隔ミリ秒
* @param targetFile 監視対象ファイル
* @param totalSize 目標とするファイルサイズ
* @param callable ファイル書き込み処理。これは別スレッドで実行される。
* @param progressNotify ファイルサイズ変更通知
* @return {@link callable}が返した値
* @throws Exception
*/
public static <T>T executeNotifyProgress(int interval, Path targetFile, long totalSize,
Callable<T>callable, Consumer<Double>progressNotify) throws Exception {
ExecutorService service = Executors.newSingleThreadExecutor();
Future<T>future = service.submit(callable);
service.shutdown();
notifyProgress(interval, targetFile, totalSize, future, progressNotify);
return future.get();
}
public static <T> void notifyProgress(int interval, Path targetFile, long totalSize,
Future<T>future, Consumer<Double>progressNotify) {
notifyProgress(interval, targetFile, totalSize, ()->future.isDone(), progressNotify);
}
/**
* ファイルサイズ増加のパーセンテージを通知する。
* 指定サイズを仮のトータルとする。
* @param interval 通知間隔、ミリ秒
* @param target 監視ファイル
* @param totalSize 仮の全体サイズ、このサイズになったらフルとする。
* @param finishedCheck 終了チェック。
* @param progressNotify パーセンテージ通知先
*/
public static void notifyProgress(int interval, Path target, long totalSize, Supplier<Boolean>finishedCheck, Consumer<Double>progressNotify) {
long[]notified = new long[] { -1 };
notifyFileSize(interval, target, finishedCheck, fileSize-> {
fileSize = Math.min(fileSize, totalSize);
if (notified[0] == fileSize) return;
notified[0] = fileSize;
double progress = (double)fileSize / totalSize;
progressNotify.accept(progress);
});
}
/**
* 現在のスレッドで監視ファイルサイズの変化を通知する。
* 終了チェックがtrueを返したときに、制御側に処理を戻す。
* @param interval 通知間隔、ミリ秒
* @param target 監視ファイル
* @param finishedCheck 終了チェック。
* @param sizeNotify ファイルサイズ通知報告先
*/
public static void notifyFileSize(int interval, Path target, Supplier<Boolean>finishedCheck, Consumer<Long>sizeNotify) {
while (!finishedCheck.get()) {
long fileSize = 0;
try {
fileSize = Files.size(target);
} catch (IOException ex) {
}
sizeNotify.accept(fileSize);
try {
Thread.sleep(interval);
} catch (InterruptedException ex) {
}
}
}
}