web-dev-qa-db-ja.com

Files.walk()、合計サイズを計算する

ディスク上のファイルのサイズを計算しようとしています。 Java-7では、これは Files.walkFileTree を使用して行うことができます。私の回答 here を参照してください。

ただし、Java-8ストリームを使用してこれを実行したい場合は、一部のフォルダーでは機能しますが、すべてでは機能しません。

public static void main(String[] args) throws IOException {
    long size = Files.walk(Paths.get("c:/")).mapToLong(MyMain::count).sum();
    System.out.println("size=" + size);
}

static long count(Path path) {
    try {
        return Files.size(path);
    } catch (IOException | UncheckedIOException e) {
        return 0;
    }
}

上記のコードはパスa:/files/c:/例外をスローします

Exception in thread "main" Java.io.UncheckedIOException: Java.nio.file.AccessDeniedException: c:\$Recycle.Bin\S-1-5-20
at Java.nio.file.FileTreeIterator.fetchNextIfNeeded(Unknown Source)
at Java.nio.file.FileTreeIterator.hasNext(Unknown Source)
at Java.util.Iterator.forEachRemaining(Unknown Source)
at Java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
at Java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at Java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at Java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
at Java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at Java.util.stream.LongPipeline.reduce(Unknown Source)
at Java.util.stream.LongPipeline.sum(Unknown Source)
at MyMain.main(MyMain.Java:16)

私はそれがどこから来ているのか、またFiles.walkFileTree APIを使用してそれを回避する方法を理解しています。

しかし、この例外は Files.walk() APIを使用してどのように回避できますか?

22
Aksel Willgert

いいえ、この例外は避けられません。

例外自体はFiles.walk()の遅延フェッチの内部で発生するため、なぜそれが早期に見られず、なぜそれを回避する方法がないのか、次のコードを検討してください。

_long size = Files.walk(Paths.get("C://"))
        .peek(System.out::println)
        .mapToLong(this::count)
        .sum();
_

私のシステムでは、これは私のコンピュータで印刷されます:

_C:\
C:\$Recycle.Bin
Exception in thread "main" Java.io.UncheckedIOException: Java.nio.file.AccessDeniedException: C:\$Recycle.Bin\S-1-5-18
_

3番目のファイルの(メイン)スレッドで例外がスローされると、そのスレッドでの以降の実行はすべて停止します。

現状では_Files.walk_は絶対に使用できないため、これは設計上の失敗だと思います。ディレクトリをウォークスルーしてもエラーが発生しないことは保証できないためです。

注意すべき重要な点の1つは、スタックトレースにsum()およびreduce()演算が含まれていることです。これは、パスが遅延読み込みされるため、reduce()のポイントで、ストリーム機構の大部分が呼び出され(スタックトレースに表示されます)、パスがフェッチされます。この時点でUnCheckedIOExceptionが発生します。

すべてのウォーキング操作を独自のスレッドで実行させると、おそらくが回避される可能性があります。しかし、それはとにかくやりたいことではありません。

また、ファイルが実際にアクセス可能かどうかのチェックは価値のない(ある程度役立つものの)です。これは、1ms後でもファイルが読み取り可能であることを保証できないためです。

将来の拡張

FileVisitOptionsが正確にどのように機能するかはわかりませんが、まだ修正できると思います。
現在_FileVisitOption.FOLLOW_LINKS_があり、それがファイル単位で動作する場合、_FileVisitOption.IGNORE_ON_IOEXCEPTION_も追加できると思いますが、その機能をそこに正しく挿入することはできません。

26
skiwi

2017年にここに到着し続ける人のために。

次の場合にFiles.walk()を使用ファイルシステムの動作は確実であり、エラーが発生したときに停止したい場合。通常、Files.walkはスタンドアロンアプリでは役に立ちません。私は頻繁にこの間違いを犯します。おそらく私は怠惰です。 100万個のファイルのような小さなもので数秒以上かかる時間を目にした瞬間、私の間違いに気づきました。

walkFileTreeをお勧めします。 FileVisitorインターフェースを実装することから始めます。ここではファイルのみをカウントします。悪いクラス名だと思います。

class Recurse implements FileVisitor<Path>{

    private long filesCount;
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
       return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        //This is where I need my logic
        filesCount++;
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        // This is important to note. Test this behaviour
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
       return FileVisitResult.CONTINUE;
    }

    public long getFilesCount() {
        return filesCount;
    }
}

次に、このように定義したクラスを使用します。

Recurse r = new Recurse();
Files.walkFileTree(Paths.get("G:"), r);
System.out.println("Total files: " + r.getFilesCount());

私が投稿した例を使用して、FileVisitor<Path>インターフェイスクラスの独自のクラスの実装を変更して、filesizeなどの他のことを行う方法を知っていると思います。この他の方法については、ドキュメントを参照してください

速度:

  • Files.walk:20分以上、例外で失敗
  • Files.walkFileTree:5.6秒、完全な回答で完了。

編集:すべての場合と同様に、例外処理の動作を確認するためにテストを使用しますが、上記のように気にしないことを選択したものを除いて、例外は引き続き発生します。

17
Abhishek Dujari

GuavaのFilesクラスを使用すると問題が解決することがわかりました。

    Iterable<File> files = Files.fileTreeTraverser().breadthFirstTraversal(dir);
    long size = toStream( files ).mapToLong( File::length ).sum();

ここで、toStreamは、IterableをStreamに変換するための静的ユーティリティ関数です。これだけ:

StreamSupport.stream(iterable.spliterator(), false);
5
Andrejs

簡単に言えば、それはできません。

例外はFileTreeWalker.visit

正確には、失敗したときにnewDirectoryStreamを構築しようとしています(このコードは制御不能です)。

// file is a directory, attempt to open it
DirectoryStream<Path> stream = null;
try {
    stream = Files.newDirectoryStream(entry);
} catch (IOException ioe) {
    return new Event(EventType.ENTRY, entry, ioe); // ==> Culprit <== 
} catch (SecurityException se) {
    if (ignoreSecurityException)
        return null;
    throw se;
}

多分あなたは バグを提出する すべきです。

3
Anthony Accioly