【问题标题】:How to read in all Files in a sub-directory using Files.walk exactly once?如何使用 Files.walk 只读取一次子目录中的所有文件?
【发布时间】:2017-06-17 22:39:27
【问题描述】:

我正在尝试读入一个目录的所有子目录中的所有文件。我已经写好了逻辑,但是我做的有点不对,因为它在每个文件中读取了两次。

为了测试我的实现,我创建了一个目录,其中包含三个子目录,每个子目录中有 10 个文档。总共应该是 30 个文档。

这是我在文档中正确阅读的测试代码:

String basePath = "src/test/resources/20NG";
Driver driver = new Driver();
List<Document> documents = driver.readInCorpus(basePath);
assertEquals(3 * 10, documents.size());

Driver#readInCorpus 的代码如下:

public List<Document> readInCorpus(String directory)
{
    try (Stream<Path> paths = Files.walk(Paths.get(directory)))
    {
        return paths
                .filter(Files::isDirectory)
                .map(this::readAllDocumentsInDirectory)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
    return Collections.emptyList();
}

private List<Document> readAllDocumentsInDirectory(Path path)
{
    try (Stream<Path> paths = Files.walk(path))
    {
        return paths
                .filter(Files::isRegularFile)
                .map(this::readInDocumentFromFile)
                .collect(Collectors.toList());
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
    return Collections.emptyList();
}

private Document readInDocumentFromFile(Path path)
{
    String fileName = path.getFileName().toString();
    String outputClass = path.getParent().getFileName().toString();
    List<String> words = EmailProcessor.readEmail(path);
    return new Document(fileName, outputClass, words);
}

当我运行测试用例时,我看到 assertEquals 失败,因为检索到 60 个文档,而不是 30 个,这是不正确的。当我调试的时候,文档都被插入到列表中一次,然后以完全相同的顺序再次插入。

在我的代码中,我在哪里阅读了两次文档?

【问题讨论】:

    标签: java file-io java-8 java-stream


    【解决方案1】:

    这里的问题在于Files.walk(path) 方法。你使用不正确。所以它像一棵树一样遍历你的文件系统。 例如,您有 3 个文件夹 - /parent 和 2 个子文件夹 /parent/first/parent/secondFiles.walk("/parent") 将为您提供每个文件夹的树 Paths - 父级和 2 个子级,实际上这发生在您的 readInCorpus 方法中。

    然后对于每个Path,您将调用第二种方法readAllDocumentsInDirectory,同样的故事在这里像树一样遍历文件夹。

    对于带有/parent 路径的readAllDocumentsInDirectory,它将返回来自两个子文件夹/parent/first/parent/second 的所有文档,然后您还有2 个readAllDocumentsInDirectory 调用/parent/first/parent/second 返回文档从两个文件夹。

    这就是为什么你的文件加倍的原因。要修复它,您应该只使用 Paths.get(basePath) 参数调用方法 readAllDocumentsInDirectory 并删除 readInCorpus 方法。

    【讨论】:

    • 有趣。这解决了问题。当你解释它是有道理的,但我不明白Paths.walk 会自动在子目录中包含文件。谢谢!
    【解决方案2】:

    这似乎来自对PathsFiles.walk 工作原理的误解。在Driver#readInCorpus,你有如下流操作:

    return paths
            .filter(Files::isRegularFile)
            .map(this::readInDocumentFromFile)
            .collect(Collectors.toList());
    

    您的映射函数 (this::readInDocumentFromFile) 从 Paths.walk 流中每个路径的每个目录中读取所有文档,其中包括顶级目录子目录。

    这意味着路径中起始目录下的所有文件都被读取一次,然后在遍历子目录时重新读取。

    查看流并不完全清楚,但您应该查看How to debug stream().map(...) with lambda expressions? 以了解如何更好地调试流并在将来避免此问题。

    这意味着您可以跳过调用Driver#readAllDocumentsInDirectory 的中间步骤,只需在Driver#readInCorpus 中使用:

    public List<Document> readInCorpus(String directory)
    {
        try (Stream<Path> paths = Files.walk(Paths.get(directory)))
        {
            return paths
                    .filter(Files::isRegularFile)
                    .map(this::readInDocumentFromFile)
                    .collect(Collectors.toList());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-10
      • 2013-02-28
      • 2021-12-15
      • 2021-02-24
      • 2021-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多