1. Introdução
Quando você trabalha com arquivos e pastas, muitas vezes precisa selecionar apenas determinados arquivos: por exemplo, todos os arquivos ".java", todas as imagens, todos os logs de uma determinada data. Para isso, em Java (e não só) usa‑se o globbing (glob) e as expressões regulares (regex).
- Globbing — uma forma simples de descrever um padrão de nome de arquivo usando caracteres especiais (*, ?, [], {}), como no terminal do Linux ou no Windows.
- Regex — uma linguagem poderosa de expressões regulares para padrões complexos.
Exemplos de padrões glob
- *.java — todos os arquivos com extensão ".java" na pasta atual.
- **/*.java — todos os arquivos ".java" em todas as subpastas (duas estrelas — busca recursiva).
- *.{png,jpg} — todos os arquivos com extensão ".png" ou ".jpg".
- file-??.log — arquivos como "file-01.log", "file-AB.log" (dois caracteres quaisquer).
- [A-Z]*.txt — todos os arquivos ".txt" que começam com letra maiúscula.
Globbing é mais simples do que regex e, na maioria dos casos, é suficiente para filtrar arquivos.
Comparação entre glob e regex
| Característica | glob | regex |
|---|---|---|
| Simplicidade | Muito simples | Mais complexo, porém mais poderoso |
| Símbolos | |
Todos os recursos de regex |
| Exemplos | |
|
| Recursividade | |
Sem suporte embutido |
| Quando usar | Filtragem de arquivos | Validações complexas |
2. PathMatcher: filtragem de arquivos por padrão
No Java NIO (java.nio.file) utiliza‑se a interface PathMatcher para filtrar arquivos por padrão. Ela pode ser obtida via FileSystems.getDefault().getPathMatcher(...).
Sintaxe
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
- "glob:*.java" — padrão glob.
- "regex:.*\\.java" — padrão de expressão regular.
Exemplo: filtragem de arquivos em uma pasta
import java.nio.file.*;
Path dir = Paths.get("src");
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
if (matcher.matches(entry.getFileName())) {
System.out.println(entry);
}
}
}
Importante:
- matcher.matches() normalmente verifica apenas o nome do arquivo — passe entry.getFileName(), e não o caminho completo.
- Para busca recursiva, use Files.walk() ou Files.find().
Files.walk() — método do NIO2 que retorna um Stream<Path> com todos os arquivos e pastas no diretório informado, de forma recursiva, incluindo subpastas. Diferentemente de DirectoryStream, que exibe apenas o conteúdo de uma única pasta, Files.walk() permite trabalhar com a árvore de diretórios via Stream API.
Exemplo de uso:
import java.nio.file.*;
import java.util.stream.Stream;
Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // todas as subpastas recursivamente
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Exemplo com regex
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");
3. Files.newDirectoryStream: filtragem ao listar uma pasta
O método Files.newDirectoryStream() permite filtrar arquivos imediatamente por padrão ou com um filtro próprio.
Filtragem por padrão glob
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
O segundo parâmetro é um padrão glob (sem o prefixo "glob:").
Filtragem com DirectoryStream.Filter
Se precisar de uma lógica mais complexa (por exemplo, filtragem por tamanho, data, exclusão de pastas), use DirectoryStream.Filter<Path>:
DirectoryStream.Filter<Path> filter = path -> {
// Exemplo: apenas arquivos (não pastas) e não .git nem node_modules
return Files.isRegularFile(path)
&& !path.getFileName().toString().equals(".git")
&& !path.getFileName().toString().equals("node_modules");
};
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
for (Path entry : stream) {
System.out.println(entry);
}
}
4. Files.find: seleção avançada com BiPredicate
Se você precisa procurar arquivos por condições complexas (por exemplo, por data, tamanho, nome, de forma recursiva), use Files.find.
Sintaxe
Stream<Path> stream = Files.find(
startDir, // onde procurar
maxDepth, // profundidade (Integer.MAX_VALUE — recursivo)
(path, attrs) -> {
// path — caminho do arquivo
// attrs — atributos do arquivo (tamanho, data etc.)
return path.getFileName().toString().endsWith(".log")
&& attrs.size() > 1024; // apenas logs grandes
}
);
stream.forEach(System.out::println);
- O segundo parâmetro é a profundidade máxima da busca.
- O terceiro é um BiPredicate<Path, BasicFileAttributes>: retorna true se o arquivo atender ao critério.
Exemplo: excluir as pastas .git e node_modules
Stream<Path> stream = Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
// Excluímos as pastas .git e node_modules
if (name.equals(".git") || name.equals("node_modules")) return false;
// Apenas arquivos .log maiores que 1 MB
return name.endsWith(".log") && attrs.size() > 1024 * 1024;
}
);
stream.forEach(System.out::println);
5. Prática
Exemplo 1: encontrar todos os arquivos .log, exceto .git e node_modules
Files.walk(Paths.get("."))
.filter(path -> {
String name = path.getFileName().toString();
// Excluímos as pastas
if (name.equals(".git") || name.equals("node_modules")) return false;
// Apenas arquivos .log
return name.endsWith(".log");
})
.forEach(System.out::println);
Exemplo 2: encontrar todas as imagens criadas após uma determinada data
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
Instant after = Instant.parse("2024-06-01T00:00:00Z");
Files.find(
Paths.get("images"),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
return (name.endsWith(".png") || name.endsWith(".jpg"))
&& attrs.creationTime().toInstant().isAfter(after);
}
).forEach(System.out::println);
Exemplo 3: encontrar todos os arquivos grandes (maiores que 10 MB), exceto .git
Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
return !name.equals(".git") && attrs.size() > 10 * 1024 * 1024;
}
).forEach(System.out::println);
6. Detalhes úteis
Guia rápido da sintaxe do glob
- * — qualquer quantidade de quaisquer caracteres (exceto o separador /)
- ** — qualquer quantidade de pastas (funciona em Java)
- ? — exatamente um caractere qualquer
- [abc] — qualquer um dos caracteres a, b, c
- [a-z] — qualquer caractere do intervalo
- {a,b,c} — qualquer um dos valores listados (por exemplo, *.{png,jpg})
Exemplos:
- *.java — todos os arquivos ".java" na pasta atual
- **/*.java — todos os arquivos ".java" em todas as subpastas
- file-??.log — arquivos como "file-01.log", "file-AB.log"
- [A-Z]*.txt — todos os arquivos ".txt" que começam com letra maiúscula
Desempenho e vazamentos: feche o DirectoryStream!
Importante!
- DirectoryStream e os streams de Files.find/Files.walk — são recursos que precisam ser fechados.
- Use try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
// ...
}
}
- Se o stream não for fechado, pode ocorrer vazamento de recursos (por exemplo, “muitos arquivos abertos”).
- Para Files.find e Files.walk — chame close() obrigatoriamente ou use try-with-resources:
try (Stream<Path> stream = Files.find(...)) {
stream.forEach(System.out::println);
}
7. Conclusão e erros comuns
Erro nº 1: Usar um padrão glob sem entender que * não pesquisa recursivamente. Para recursão, use "**/*.java" ou Files.walk.
Erro nº 2: Passar o caminho completo para matcher.matches() — normalmente deve‑se passar apenas o nome do arquivo (getFileName()).
Erro nº 3: Esquecer de fechar DirectoryStream ou Stream<Path> — isso causa vazamento de recursos.
Erro nº 4: Padrões regex demasiadamente complexos para uma filtragem simples — use glob quando for suficiente.
Erro nº 5: Não excluir pastas de sistema (".git", "node_modules") — a busca fica lenta e “poluída” por arquivos desnecessários.
GO TO FULL VERSION