如何在Java 8中使用NIO仅列出没有进一步子目录的目录?

huangapple go评论73阅读模式
英文:

How to list directories only if there is no further subdirectory present using NIO in Java 8?

问题

使用Java 8 NIO和以下代码来列出目录:

String rootDir = "root";
Files.walk(Paths.get(rootDir))
     .filter(Files::isDirectory)
     .forEach(folder_path -> {
         System.out.println(folder_path.toString());
     });

但这将会产生以下输出:

root
root\dir1
root\dir1\subdir1
root\dir1\subdir2
root\dir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3
root\dir3\subdir1
root\dir3\subdir2

但是,如果只有没有进一步的子目录时才显示路径,你可以使用以下代码来实现预期的输出:

String rootDir = "root";
Files.walk(Paths.get(rootDir))
     .filter(path -> Files.isDirectory(path) && Files.list(path).noneMatch(Files::isDirectory))
     .forEach(folder_path -> {
         System.out.println(folder_path.toString());
     });

这将产生以下输出:

root\dir1\subdir1
root\dir1\subdir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3\subdir1
root\dir3\subdir2

这个代码会检查每个目录,仅当该目录没有子目录时才输出路径。

英文:

I am using Java 8 NIO and the below code to list directories;

String  rootDir = "root\\";
Files.walk(Paths.get(rootDir)).filter(Files::isDirectory).forEach(folder_path -> {
	System.out.println(folder_path.toString());
});

But this will give the output like below;

root
root\dir1
root\dir1\subdir1
root\dir1\subdir2
root\dir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3
root\dir3\subdir1
root\dir3\subdir2

But, I would like to display the path, only if there is no further sub-directories. So, the expected output will be like;

root\dir1\subdir1
root\dir1\subdir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3\subdir1
root\dir3\subdir2

Is this possible? If yes, how can I achieve this?

答案1

得分: 3

你可以直接实现一个过滤器,例如:

```java
Files.walk(Paths.get(rootDir))
    .filter(Files::isDirectory)
    .filter(dir -> {
       try (Stream<Path> sub = Files.list(dir)) { return sub.noneMatch(Files::isDirectory); }
       catch (IOException ex) { throw new UncheckedIOException(ex); }
    })
    .forEach(System.out::println);

这个方法简单但在每个遇到的目录上执行了冗余操作。

一个更有效的替代方法是经典的循环解决方案:

Set<Path> dirs = new LinkedHashSet<>();
for (Iterator<Path> it = Files.walk(Paths.get(rootDir)).iterator(); it.hasNext(); ) {
    Path p = it.next();
    if (Files.isDirectory(p) && dirs.add(p)) dirs.remove(p.getParent());
}
dirs.forEach(System.out::println);

它在遇到另一个目录时简单地删除父目录,因此在此单次迭代结束时,只剩下不包含其他目录的目录。

或者使用老式的Java 7 API:

Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
    Path previous;

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        if (previous == null || !previous.startsWith(dir))
            System.out.println(dir);
        previous = dir;
        return FileVisitResult.CONTINUE;
    }
});

通过重写 postVisitDirectory(而不是 preVisitDirectory),我们在访问其子目录之前访问了一个子目录,如果有的话。因此,仅需要简单地测试前一个路径是否是当前路径的子目录。


<details>
<summary>英文:</summary>

You can implement a filter straight-forwardly, e.g.

Files.walk(Paths.get(rootDir))
.filter(Files::isDirectory)
.filter(dir -> {
try(Stream<Path> sub=Files.list(dir)) { return sub.noneMatch(Files::isDirectory); }
catch(IOException ex) { throw new UncheckedIOException(ex); }
})
.forEach(System.out::println);


which is simple but performs redundant operations on each encountered directory.

A more efficient alternative would be a classic loop solution:

Set<Path> dirs = new LinkedHashSet<>();
for(Iterator<Path> it = Files.walk(Paths.get(rootDir)).iterator(); it.hasNext(); ) {
Path p = it.next();
if(Files.isDirectory(p) && dirs.add(p)) dirs.remove(p.getParent());
}
dirs.forEach(System.out::println);


It simply removes parent directories when encountering another directory, so at the end of this single iteration, only directories not containing other directories are left.

Or with the good old Java&#160;7 API:

Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
Path previous;

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
    if(previous == null || !previous.startsWith(dir))
        System.out.println(dir);
    previous = dir;
    return FileVisitResult.CONTINUE;
}

});

By overriding `postVisitDirectory` (instead `preVisitDirectory`), we have visited a child directory of it right before, if there was one. So a simple test whether the previous path is a child of the current is sufficient.

</details>



# 答案2
**得分**: 0

你可以创建一个用于检查子目录并将其添加到过滤器中的方法,如下所示:

```java
private static Predicate<Path> checkSubDirectory() {
    return f1 -> {
        for (File file : f1.toFile().listFiles()) {
            if (file.isDirectory())
                return false;
        }
        return true;
    };
}

Files.walk(Paths.get(rootDir)).filter(Files::isDirectory)
       .filter(checkSubDirectory())                      
       .forEach(System.out::println);
英文:

You can create a method to check sub-directory and add it in filter as below,

private static Predicate&lt;Path&gt; checkSubDirectory() {
    return f1-&gt;{
        for (File file : f1.toFile().listFiles()) {
            if (file.isDirectory())
                return false;
        }
        return true;
    };
}

Files.walk(Paths.get(rootDir)).filter(Files::isDirectory)
       .filter(checkSubDirectory())                      
       .forEach(System.out::println);                                              

答案3

得分: 0

Another simple version which uses streams:

LinkedHashSet dirs = new LinkedHashSet<>();
try (Stream stream = Files.find(dir, Integer.MAX_VALUE, (path, attr) -> attr.isDirectory())) {
stream.filter(dirs::add).map(Path::getParent).forEach(dirs::remove);
}

英文:

Another simple version which uses streams:

LinkedHashSet&lt;Path&gt; dirs = new LinkedHashSet&lt;&gt;();
try(Stream&lt;Path&gt; stream = Files.find(dir, Integer.MAX_VALUE, (path,attr) -&gt; attr.isDirectory())) {
    stream.filter(dirs::add).map(Path::getParent).forEach(dirs::remove);
}

huangapple
  • 本文由 发表于 2020年7月31日 18:26:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/63190101.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定