Files.walk(), вычислить общий размер

Я пытаюсь рассчитать размер файлов на моем диске. В java-7 это можно сделать, используя Files.walkFileTree, как показано в моем ответе здесь.

Однако, если бы я хотел это сделать, используя потоки 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)

Я понимаю, откуда это происходит, и как его избежать, используя API Files.walkFileTree.

Но как избежать этого исключения с помощью Files.walk() API?

Ответ 1

Нет, этого исключения нельзя избежать.

Само исключение происходит внутри ленивого извлечения 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

И поскольку исключение выбрасывается в (основной) поток третьего файла, все дальнейшие исполнения на этом потоке останавливаются.

Я считаю, что это сбой дизайна, потому что, поскольку он стоит сейчас, Files.walk абсолютно непригоден, потому что вы никогда не можете гарантировать, что при переходе по каталогу не будет ошибок.

Важно отметить, что stacktrace включает в себя операцию sum() и reduce(), это потому, что путь загружается лениво, так что в точке reduce() основная часть машины потока называется ( видимый в stacktrace), а затем он выбирает путь, после которого происходит UnCheckedIOException.

Это можно было бы обойти, если бы вы разрешили каждую операцию ходьбе выполнять свою собственную нить. Но это не то, что вы хотели бы делать в любом случае.

Кроме того, проверка того, действительно ли файл доступен, бесполезен (хотя и полезен до некоторой степени), потому что вы не можете гарантировать, что он доступен для чтения даже через 1 м.

Будущее расширение

Я считаю, что он все еще может быть исправлен, хотя я не знаю, как именно работать FileVisitOption.
В настоящее время существует FileVisitOption.FOLLOW_LINKS, если он работает на основе каждого файла, тогда я бы заподозрил, что может быть добавлен FileVisitOption.IGNORE_ON_IOEXCEPTION, однако мы не можем правильно ввести туда эту функциональность.

Ответ 2

2017 для тех, кто продолжает прибывать сюда.

Использовать Files.walk(), когда вы уверены в поведении файловой системы и действительно хотите остановиться, когда есть какая-либо ошибка. Как правило, Files.walk не используется в автономных приложениях. Я часто делаю эту ошибку, возможно, я ленив. Я осознаю свою ошибку в тот момент, когда вижу, что время, потраченное более нескольких секунд на что-то маленькое, как 1 миллион файлов.

Я рекомендую 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());

Я уверен, что вы знаете, как изменить реализацию класса class класса FileVisitor<Path> своего собственного класса, чтобы сделать другие вещи, например filesize, с приведенным выше примером. Обратитесь к документам для других методов в этом

Скорость:

  • Files.walk: 20+ минут и сбой с исключением
  • Files.walkFileTree: 5.6 секунды, выполненный с отличным ответом.

Изменить: Как и все, используйте тесты для подтверждения поведения Исключайте дескрипторы, они все еще встречаются, за исключением тех, которые мы предпочитаем не заботиться, как описано выше.

Ответ 3

Короткий ответ: вы не можете.

Исключение исходит от 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;
}

Возможно, вы должны отправить ошибку.

Ответ 4

Я обнаружил, что использование класса Guava Files решило проблему для меня:

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

Где toStream - моя статическая функция полезности для преобразования Iterable в поток. Только это:

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