列出目录中与文件掩码(a.k.a 模式或 Glob)匹配的所有文件
List all Files from a Directory that match a File Mask (a.k.a Pattern or Glob)
我想列出目录中的所有文件和该目录中匹配文件掩码的子目录 =36=].
例如“M:\SOURCE\*.doc”,而 SOURCE 可能如下所示:
|-- SOURCE
| |-- Folder1
| | |-- File1.doc
| | |-- File1.txt
| |-- File2.doc
| |-- File3.xml
应该returnFile1.doc和File2.doc.
最初,我使用 DirectoryStream,因为它已经对 mask/glob 语法进行了一些检查,并且能够像这样使用它进行过滤 ISN'T 只是一些正则表达式,但普通用户更容易理解的实际文件掩码
Files.newDirectoryStream(path, mask);
问题是 DirectoryStream 只检查您提供的直接路径目录,不是它的子目录
THEN 带有 Files.walk 的“扁平化”方法实际上能够查看所有子目录,问题是,它不提供以与 DirectoryStream 相同的方式通过文件掩码“过滤”的可能性
Files.walk(path, Integer.MAX_VALUE);
所以我卡住了,无法在这里结合两种方法的优点...
可以使用通用流 filter
通过 String::matches
和适当的正则表达式从 Files.walk
检索过滤的文件名:
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
您还可以使用自定义 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
我想我可能已经用这里收到的见解和其他提到 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 过滤文件
的工作相同
在那之后我现在要做的就是过滤我用 Files.walk 获得的路径列表,方法是删除与我的 PathMatcher 不匹配并且是不是文件类型
我想列出目录中的所有文件和该目录中匹配文件掩码的子目录 =36=].
例如“M:\SOURCE\*.doc”,而 SOURCE 可能如下所示:
|-- SOURCE
| |-- Folder1
| | |-- File1.doc
| | |-- File1.txt
| |-- File2.doc
| |-- File3.xml
应该returnFile1.doc和File2.doc.
最初,我使用 DirectoryStream,因为它已经对 mask/glob 语法进行了一些检查,并且能够像这样使用它进行过滤 ISN'T 只是一些正则表达式,但普通用户更容易理解的实际文件掩码
Files.newDirectoryStream(path, mask);
问题是 DirectoryStream 只检查您提供的直接路径目录,不是它的子目录
THEN 带有 Files.walk 的“扁平化”方法实际上能够查看所有子目录,问题是,它不提供以与 DirectoryStream 相同的方式通过文件掩码“过滤”的可能性
Files.walk(path, Integer.MAX_VALUE);
所以我卡住了,无法在这里结合两种方法的优点...
可以使用通用流 filter
通过 String::matches
和适当的正则表达式从 Files.walk
检索过滤的文件名:
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
您还可以使用自定义 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
我想我可能已经用这里收到的见解和其他提到 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 过滤文件
在那之后我现在要做的就是过滤我用 Files.walk 获得的路径列表,方法是删除与我的 PathMatcher 不匹配并且是不是文件类型