Files.walk()计算总大小

我正在尝试计算光盘上文件的大小。 在java-7中,这可以使用Files.walkFileTree来完成,如我的答案所示。

但是,如果我想要使用java-8stream来完成这项工作,它将适用于某些文件夹,但不是全部。

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

上面的代码将适用于patha:/files/但对于c:/它会抛出exception

 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避免这个exception呢?

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

这个exception本身发生在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 

而当第三个文件的(主)线程抛出一个exception时,该线程上的所有进一步执行就停止。

我相信这是一个devise失败,因为现在Files.walk是绝对无法使用的,因为你永远不能保证走过目录时不会有错误。

需要注意的一点是stacktrace包含sum()reduce()操作,这是因为path被延迟加载,所以在reduce()点,大量的stream机制被调用(在堆栈跟踪中可见),然后获取UnCheckedIOException发生的path。

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

此外,检查一个文件是否实际可访问是毫无价值的 (尽pipe在某种程度上是有用的),因为即使在1ms之后也不能保证它是可读的。

未来的延伸

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

2017为那些一直到这里来。

确定文件系统行为时,使用Files.walk(),并在出现任何错误时确实要停止。 一般来说,Files.walk在独立应用程序中没有用处。 我经常犯这个错误,也许我很懒。 我意识到我的错误,当我看到时间超过几秒钟,像一百万个小文件一样。

我build议walkFileTree 。 首先实现FileVisitor接口,这里我只想计算文件。 我知道,class级名字不好。

 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类的class的实现来完成其他事情,比如我发布的例子中的filesize 。 请参考文档中的其他方法

速度:

  • Files.walk:20分钟以上,例外
  • Files.walkFileTree:5.6秒,用完美的答案完成。

编辑:与所有事情一样,使用testing来确认Handle Exceptions的行为,除了那些我们select不关心的事情之外,它们仍然会发生。

简短的答案是你不能。

例外来自FileTreeWalker.visit

确切地说,它试图在失败时创build一个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; } 

也许你应该提交一个错误 。

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

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

哪里toStream是我的静态实用function将Iterable转换为Stream。 只是这个:

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