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