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

java nio java-8 java-stream

12647 просмотра

4 ответа

Я пытаюсь рассчитать размер файлов на моем диске. В 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.

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

Автор: Aksel Willgert Источник Размещён: 13.11.2019 11:33

Ответы (4)


25 плюса

Решение

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

Само исключение происходит внутри ленивого извлечения 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можно также добавить a , однако мы не можем корректно внедрить эту функциональность туда.

Автор: skiwi Размещён: 04.04.2014 04:57

16 плюса

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

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

Я рекомендую 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>класса Interface вашего класса, чтобы сделать другие вещи, как filesizeв примере, который я опубликовал. Обратитесь к документации для других методов в этом

Скорость:

  • Files.walk: 20 с лишним минут и ошибка за исключением
  • Files.walkFileTree: 5,6 секунды, сделано с идеальным ответом.

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

Автор: Abhishek Dujari Размещён: 09.01.2017 06:16

5 плюса

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

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

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

StreamSupport.stream(iterable.spliterator(), false);
Автор: Andrejs Размещён: 15.07.2014 11:52

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;
}

Может быть, вы должны представить ошибку .

Автор: Anthony Accioly Размещён: 04.04.2014 05:01
Вопросы из категории :
32x32