【问题标题】:List all Files from a Directory that match a File Mask (a.k.a Pattern or Glob)列出目录中与文件掩码匹配的所有文件(a.k.a Pattern 或 Glob)
【发布时间】:2020-11-13 19:15:18
【问题描述】:

我想列出目录中的所有文件以及该目录中与文件掩码匹配的子目录

例如“M:\SOURCE\*.doc”,而 SOURCE 可能如下所示:

|-- SOURCE
|   |-- Folder1
|   |   |-- File1.doc
|   |   |-- File1.txt
|   |-- File2.doc
|   |-- File3.xml

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

最初,我使用 DirectoryStream,因为它已经对 mask/glob 语法进行了一些检查,并且能够将其用于过滤,因为这 ISN'T 只是一些正则表达式,而是一个普通用户更容易理解的实际文件掩码

Files.newDirectoryStream(path, mask);

问题是 DirectoryStream 只检查您提供的直接路径目录,不是子目录

THEN 带有 Files.walk 的“扁平化”方法,它实际上可以查看所有子目录,问题是,它不提供可以像 DirectoryStream 一样通过文件掩码“过滤”

Files.walk(path, Integer.MAX_VALUE);

所以我被卡住了,无法在这里结合两种方法中最好的...

【问题讨论】:

    标签: java file-io nio directorystream


    【解决方案1】:

    您还可以使用自定义FileVisitor [1],结合PathMatcher [2],与GLOB 完美配合。

    代码可能如下所示:

    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]https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileVisitor.html

    [2]https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/PathMatcher.html

    【讨论】:

      【解决方案2】:

      我想我可能已经通过此处收到的见解和其他提到 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 不匹配且不是 File 类型的元素来过滤我使用 Files.walk 获得的路径列表

      【讨论】:

        【解决方案3】:

        可以使用普通的 Stream filterFiles.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”示例,但这并不是唯一可以使用的文件掩码,因此,我必须将每个文件掩码转换为正确的正则表达式。
        • 使用通用文件掩码添加递归解决方案
        猜你喜欢
        • 2014-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-01
        • 2018-01-11
        • 1970-01-01
        • 2014-05-02
        • 2011-03-07
        相关资源
        最近更新 更多