Files.walk(),计算总大小

Aks*_*ert 21 java nio java-8 java-stream

我正在尝试计算光盘上文件的大小.在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码适用于路径,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)
Run Code Online (Sandbox Code Playgroud)

我了解它的来源以及如何使用Files.walkFileTree API来避免它.

但是如何使用Files.walk() API 避免这种异常呢?

ski*_*iwi 25

不,这个例外是无法避免的.

异常本身发生在lazy fetch中Files.walk(),因此为什么你没有及早看到它以及为什么没有办法绕过它,请考虑以下代码:

long size = Files.walk(Paths.get("C://"))
        .peek(System.out::println)
        .mapToLong(this::count)
        .sum();
Run Code Online (Sandbox Code Playgroud)

在我的系统上,这将在我的计算机上打印:

C:\
C:\$Recycle.Bin
Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException: C:\$Recycle.Bin\S-1-5-18
Run Code Online (Sandbox Code Playgroud)

并且当在第三个文件的(主)线程上抛出异常时,该线程上的所有进一步执行都会停止.

我相信这是一个设计失败,因为它现在Files.walk已经绝对无法使用,因为你无法保证在目标上行走时不会出现错误.

需要注意的一点是,堆栈跟踪包括一个sum()reduce()操作,这是因为路径被延迟加载,所以在这一点上reduce(),大量的流机制被调用(在堆栈跟踪中可见),然后它获取路径,在哪一点UnCheckedIOException发生.

如果让每个步行操作都在自己的线程上执行,则可能被绕过.但这不是你想要做的事情.

此外,检查文件是否实际可访问是没有价值的(尽管在某种程度上有用),因为您无法保证即使在1ms之后它也是可读的.

未来的扩展

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

  • 是的,同意了.这是设计失败. (5认同)
  • 是的,好的分析,+ 1.覆盖此错误(扩展请求)是[JDK-8039910](https://bugs.openjdk.java.net/browse/JDK-8039910). (5认同)
  • 6 年后,该错误以“未来项目”决议结束,并且没有后者的迹象:( (3认同)

Abh*_*ari 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用你定义的类.

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

我相信你知道如何修改你自己的类的实现FileVisitor<Path>Interface类来做其他事情filesize,比如我发布的例子.有关此处的其他方法,请参阅文档

速度:

  • Files.walk:超过20分钟并且出现异常失败
  • Files.walkFileTree:5.6秒,完成答案.

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

  • 最好扩展 `SimpleFileVisitor` (抽象类)而不是 `FileVisitor` (接口)。但是,您必须重写“visitFileFailed()”,因为具有讽刺意味的是,默认实现模仿“Files.walk()”。 (2认同)

And*_*ejs 5

我发现使用Guava的Files类为我解决了这个问题:

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

toStream我的静态实用程序函数将Iterable转换为Stream的位置在哪里。只是这个:

StreamSupport.stream(iterable.spliterator(), false);
Run Code Online (Sandbox Code Playgroud)