如何使用Files.walk()...根据条件获取文件图形?

26

我有以下目录结构:

/path/to/stuff/org/foo/bar/
/path/to/stuff/org/foo/bar/1.2.3/
/path/to/stuff/org/foo/bar/1.2.3/myfile.ext
/path/to/stuff/org/foo/bar/1.2.4/
/path/to/stuff/org/foo/bar/1.2.4/myfile.ext
/path/to/stuff/org/foo/bar/blah/
/path/to/stuff/org/foo/bar/blah/2.1/
/path/to/stuff/org/foo/bar/blah/2.1/myfile.ext
/path/to/stuff/org/foo/bar/blah/2.2/
/path/to/stuff/org/foo/bar/blah/2.2/myfile.ext

我希望获得以下输出:

/path/to/stuff/org/foo/bar/
/path/to/stuff/org/foo/bar/blah/

我有以下代码(如下所示),效率不高,因为它会打印出:

/path/to/stuff/org/foo/bar/
/path/to/stuff/org/foo/bar/
/path/to/stuff/org/foo/bar/blah/
/path/to/stuff/org/foo/bar/blah/

以下是 Java 代码:

public class LocatorTest
{

    @Test
    public void testLocateDirectories()
            throws IOException
    {
        long startTime = System.currentTimeMillis();

        Files.walk(Paths.get("/path/to/stuff/"))
             .filter(Files::isDirectory)
             .forEach(Foo::printIfArtifactVersionDirectory);

        long endTime = System.currentTimeMillis();

        System.out.println("Executed in " + (endTime - startTime) + " ms.");
    }

    static class Foo
    {

        static void printIfArtifactVersionDirectory(Path path)
        {
            File f = path.toAbsolutePath().toFile();
            List<String> filePaths = Arrays.asList(f.list(new MyExtFilenameFilter()));

            if (!filePaths.isEmpty())
            {
                System.out.println(path.getParent());
            }
        }

    }

}

过滤器:

public class MyExtFilenameFilter
        implements FilenameFilter
{

    @Override
    public boolean accept(File dir, String name)
    {
        return name.endsWith(".ext");
    }

}

1
.allMatch() 无法编译。 - Seelenvirtuose
1
@Seelenvirtuose:非常抱歉,已经修复了。 - carlspring
3个回答

41
Files.walk(Paths.get("/path/to/stuff/"))
     .filter(p -> p.toString().endsWith(".ext"))
     .map(p -> p.getParent().getParent())
     .distinct()
     .forEach(System.out::println);

这将过滤所有具有该扩展名的文件,并获取其目录的父路径。distinct确保每个路径仅使用一次。


我建议接受这个答案,因为它非常优雅,并且很好地使用了Java 8的特性。 - S. Pauk
1
为什么不使用 .filter(p -> p.endsWith(".ext")) - user2336315
7
因为它只考虑了路径中的目录部分,而没有考虑文件部分。 - a better oliver
1
不要忘记关闭流,例如使用 try-with-resources - Theo

4
您正在调用方法printIfArtifactVersionDirectory以访问所有目录。我做了一点修改,使它更加明显:
static void printIfArtifactVersionDirectory(Path path) {
    System.out.println("--- " + path);
    ...
}

使用这个附加输出,您将得到以下内容:
--- C:\Projects\stuff
--- C:\Projects\stuff\org
--- C:\Projects\stuff\org\foo
--- C:\Projects\stuff\org\foo\bar
--- C:\Projects\stuff\org\foo\bar\1.2.3
C:\Projects\stuff\org\foo\bar
--- C:\Projects\stuff\org\foo\bar\1.2.4
C:\Projects\stuff\org\foo\bar
--- C:\Projects\stuff\org\foo\bar\blah
--- C:\Projects\stuff\org\foo\bar\blah\2.1
C:\Projects\stuff\org\foo\bar\blah
--- C:\Projects\stuff\org\foo\bar\blah\2.2
C:\Projects\stuff\org\foo\bar\blah
因此,您将根据构件版本目录的数量获得相应的输出。如果您想要“记住”已经输出了一个目录的信息,您必须在某个地方存储这些信息。一种快速实现方法可以是:
static class Foo {
    private static final Set<Path> visited = new HashSet<>();

    static void printIfArtifactVersionDirectory(Path path) {
        ...
        Path parent = path.getParent();
        if (!filePaths.isEmpty() && !visited.contains(parent)) {
            visited.add(parent);
            System.out.println(parent);
        }
    }
}

使用这个代码,你可以得到预期的输出结果:

C:\Projects\stuff\org\foo\bar
C:\Projects\stuff\org\foo\bar\blah

更好的解决方案是使用集合来存储已访问的父级,只有在访问完所有父级后才打印它们:

static class PathStore {
    private final Set<Path> store = new HashSet<>();

    void visit(Path path) {
        File f = path.toAbsolutePath().toFile();
        List<String> filePaths = Arrays.asList(f.list(new MyExtFilenameFilter()));
        if (!filePaths.isEmpty()) {
            store.add(path.getParent());
        }
    }

    void print() {
        store.forEach(System.out::println);
    }
}

使用方法:

PathStore pathStore = new PathStore();
Files.walk(Paths.get("/path/to/stuff/"))
        .filter(Files::isDirectory)
        .forEach(pathStore::visit);
pathStore.print();

1
嗨,感谢你的回答!你的回答非常出色,我将在其中一个案例中以这种方式使用它(其中会有后续操作)。然而,我会选择另一个,因为大多数情况下会像这样使用它,因为它更简单,更优雅。非常感谢! - carlspring

1

@a-better-oliver's answer的基础上,如果您的操作声明了IOExceptions,您可以这样做。

您可以将过滤后的流视为Iterable,然后在常规的for-each循环中执行操作。这样,您就不必在lambda内部处理异常。

try (Stream<Path> pathStream = Files
        .walk(Paths.get("/path/to/stuff/"))
        .filter(p -> p.toString().endsWith(".ext"))
        .map(p -> p.getParent().getParent())
        .distinct()) {

    for (Path file : (Iterable<Path>) pathStream::iterator) {
        // something that throws IOException
        Files.copy(file, System.out);
    }
}

我在这里找到了窍门:https://dev59.com/gmAg5IYBdhLWcg3wdq4y#32668807


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接