Files.walk(),计算总大小

2022-09-01 18:20:21

我正在尝试计算光盘上文件的大小。在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)

我知道它来自哪里以及如何使用Files.walkFileTree API避免它。

但是如何使用 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 包含一个 and 操作,这是因为路径被懒惰地加载,所以在 的点上,大部分流机械被调用(在 stacktrace 中可见),然后它获取路径,此时发生。sum()reduce()reduce()UnCheckedIOException

如果您让每个步行操作在自己的线程上执行,则可能会绕过它。但无论如何,这不是你想做的事情。

此外,检查文件是否实际可访问是毫无价值的(尽管在某种程度上很有用),因为您不能保证它甚至在1ms后可读。

未来扩展

我相信它仍然可以修复,尽管我不知道s究竟是如何工作的。
目前有一个,如果它以每个文件为基础运行,那么我怀疑也可以添加一个,但是我们无法在那里正确注入该功能。FileVisitOptionFileVisitOption.FOLLOW_LINKSFileVisitOption.IGNORE_ON_IOEXCEPTION


答案 2

2017年对于那些不断来到这里的人。

当您确定文件系统行为并且确实希望在出现任何错误时停止时,请使用 Files.walk()。通常,Files.walk 在独立应用中没有用处。我经常犯这个错误,也许我很懒惰。当我看到像100万个文件这样的小文件所花费的时间持续超过几秒钟时,我意识到了我的错误。

我推荐。首先实现FileVisitor接口,在这里我只想计算文件。糟糕的班级名称,我知道。walkFileTree

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秒,完成完美的答案。

编辑:与所有内容一样,使用测试来确认行为处理异常,它们仍然会发生,除了我们选择不关心的那些如上所述。


推荐