Files.walk(),计算总大小

时间:2014-04-04 15:43:29

标签: java nio java-8 java-stream

我试图计算光盘上文件的大小。在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避免此异常?

4 个答案:

答案 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之类的其他操作。有关此

中的其他方法,请参阅文档

速度:

  • Files.walk:20多分钟,但没有例外
  • Files.walkFileTree:5.6秒,完美答案。

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

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