我试图计算光盘上文件的大小。在java-7中,这可以使用Files.walkFileTree完成,如我的回答here所示。
但是,如果我想使用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避免此异常?
答案 0 :(得分:23)
不,这个例外是无法避免的。
异常本身发生在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
绝对无法使用,因为你无法保证在目标上行走时不会出现错误。
需要注意的一点是,堆栈跟踪包含sum()
和reduce()
操作,这是因为路径被延迟加载,所以在reduce()
点,批量流机制被调用(在堆栈跟踪中可见),然后它获取路径,此时UnCheckedIOException
发生。
如果让每个步行操作都在自己的线程上执行,那么可能可以被规避。但这不是你想要做的事情。
另外,检查文件是否实际可访问毫无价值(虽然在某种程度上有用),因为你不能保证它在1ms之后可读。
我相信它仍然可以修复,但我不知道FileVisitOption
究竟是如何工作的
目前有FileVisitOption.FOLLOW_LINKS
,如果它基于每个文件运行,那么我怀疑还可以添加FileVisitOption.IGNORE_ON_IOEXCEPTION
,但是我们无法在那里正确地注入该功能。
答案 1 :(得分:16)
2017年对于那些一直到达这里的人。
使用Files.walk()时您确定文件系统行为,并且确实想要在出现任何错误时停止。通常,Files.walk在独立应用程序中没用。我经常犯这个错误,也许我很懒。我意识到自己的错误,当我看到时间持续超过几秒钟的时候就像100万个文件一样。
我推荐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>
接口类的实现,以便使用我发布的示例执行filesize
之类的其他操作。有关此
速度:
编辑: 与所有内容一样,使用测试来确认行为 处理异常,除了我们选择不关注的那些外,它们仍然会发生。
答案 2 :(得分: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;
}
也许你应该submit a bug。
答案 3 :(得分:3)
我发现使用Guava的Files类为我解决了这个问题:
Iterable<File> files = Files.fileTreeTraverser().breadthFirstTraversal(dir);
long size = toStream( files ).mapToLong( File::length ).sum();
toStream
是我的静态实用程序函数,用于将Iterable转换为Stream。就是这样:
StreamSupport.stream(iterable.spliterator(), false);