列出与文件掩码(也称为模式或通配符)匹配的目录中所有文件。

3
我想列出一个目录及其子目录下,所有符合给定文件匹配模式的文件列表。

例如,"M:\SOURCE\*.doc",其中 SOURCE 目录的结构如下:
|-- SOURCE
|   |-- Folder1
|   |   |-- File1.doc
|   |   |-- File1.txt
|   |-- File2.doc
|   |-- File3.xml

应该返回File1.doc和File2.doc。

一开始,我使用了DirectoryStream,因为它已经对掩码/ glob语法进行了一些检查,并且能够将其用于过滤器,因为这不仅仅是一些正则表达式,而是实际的文件掩码,更容易让普通用户理解。

Files.newDirectoryStream(path, mask);

问题在于DirectoryStream只检查您提供的直接路径目录,而不是它的子目录。
然后有一个"扁平化"方法,使用Files.walk可以查看所有子目录,但问题是它不能像DirectoryStream一样提供按文件掩码过滤的可能性。
Files.walk(path, Integer.MAX_VALUE);

所以我陷入了困境,无法将这两种方法的优点结合起来...

3个回答

3
你可以使用自定义的FileVisitor [1],结合PathMatcher [2],它能完美地与GLOBs配合使用。
代码可能如下所示:
public static void main(String[] args) throws IOException {
    System.out.println(getFiles(Paths.get("/tmp/SOURCE"), "*.doc"));
}

public static List<Path> getFiles(final Path directory, final String glob) throws IOException {
    final var docFileVisitor = new GlobFileVisitor(glob);
    Files.walkFileTree(directory, docFileVisitor);

    return docFileVisitor.getMatchedFiles();
}

public static class GlobFileVisitor extends SimpleFileVisitor<Path> {

    private final PathMatcher pathMatcher;
    private List<Path> matchedFiles = new ArrayList<>();

    public GlobFileVisitor(final String glob) {
        this.pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
    }

    @Override
    public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) throws IOException {
        if (pathMatcher.matches(path.getFileName())) {
            matchedFiles.add(path);
        }
        return FileVisitResult.CONTINUE;
    }

    public List<Path> getMatchedFiles() {
        return matchedFiles;
    }
}

[1] FileVisitor是Java中的一个接口,用于遍历文件和目录。它提供了在访问文件树节点时要执行的方法,包括在进入目录之前、在离开目录之后和访问文件时需要执行的操作。可以使用该接口来实现自定义的遍历方式。
[2] PathMatcher是一个Java接口,用于匹配文件路径的模式。它提供了对文件系统路径进行比较的方法,以确定是否与指定的模式相匹配。可以使用正则表达式或通过指定通配符来创建模式,以便在进行路径匹配时更加灵活。

1

我认为通过这里得到的见解和其他提到PathMatcher对象的问题,我可能已经解决了自己的问题。

final PathMatcher maskMatcher = FileSystems.getDefault()
                  .getPathMatcher("glob:" + mask);

final List<Path> matchedFiles = Files.walk(path)
                  .collect(Collectors.toList());

final List<Path> filesToRemove = new ArrayList<>(matchedFiles.size());

matchedFiles.forEach(foundPath -> {
            if (!maskMatcher.matches(foundPath.getFileName()) || Files.isDirectory(foundPath)) {
              filesToRemove.add(foundPath);
            }
          });

 matchedFiles.removeAll(filesToRemove);

基本上,.getPathMatcher("glob:" + mask); 就是 DirectoryStream 用来过滤文件的相同操作。
现在,我所要做的就是通过删除不匹配我的 PathMatcher 且不是文件类型的元素来过滤使用 Files.walk 获取的路径列表。

0

可以使用常见的流filter来使用适当的正则表达式从Files.walk中检索过滤后的文件名,方法是使用String::matches

final String SOURCE_DIR = "test";

Files.walk(Paths.get(SOURCE_DIR));
     .filter(p -> p.getFileName().toString().matches(".*\\.docx?"))
     .forEach(System.out::println);

输出

test\level01\level11\test.doc
test\level02\test-level2.doc
test\t1.doc
test\t3.docx

输入目录结构:

│   t1.doc
│   t2.txt
│   t3.docx
│   t4.bin
│
├───level01
│   │   test.do
│   │
│   └───level11
│           test.doc
│
└───level02
        test-level2.doc

更新

使用newDirectoryStream可以实现递归解决方案,但需要将其转换为流:

static Stream<Path> readFilesByMaskRecursively(Path start, String mask) {
        
    List<Stream<Path>> sub = new ArrayList<>();
        
    try {
        sub.add(StreamSupport.stream( // read files by mask in current dir
                Files.newDirectoryStream(start, mask).spliterator(), false));
            
        Files.newDirectoryStream(start, (path) -> path.toFile().isDirectory())
             .forEach(path -> sub.add(recursive(path, mask)));
    } catch (IOException ioex) {
        ioex.printStackTrace();
    }
        
    return sub.stream().flatMap(s -> s); // convert to Stream<Path>
}

// test
readFilesByMaskRecursively(Paths.get(SOURCE_DIR), "*.doc*")
             .forEach(System.out::println);

输出:

test\t1.doc
test\t3.docx
test\level01\level11\test.doc
test\level02\test-level2.doc

更新2

可以在PathMatcher前加上前缀**/以跨越目录边界,然后Files.walk解决方案可以使用简化的过滤器,无需删除特定条目:

String mask = "*.doc*";
PathMatcher maskMatcher = FileSystems.getDefault().getPathMatcher("glob:**/" + mask);
Files.walk(Paths.get(SOURCE_DIR))
     .filter(path -> maskMatcher.matches(path))
     .forEach(System.out::println);

输出(与递归解决方案相同):

test\level01\level11\test.doc
test\level02\test-level2.doc
test\t1.doc
test\t3.docx

“使用适当的正则表达式”,这是个问题,我不想处理正则表达式,我希望用户输入的文件掩码能够立即起作用。我提到了“*.doc”示例,但这并不是唯一可用的文件掩码,因此我必须将每个掩码转换为适当的正则表达式。 - Jeyson Ardila
添加了使用通用文件掩码的递归解决方案。 - Nowhere Man

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