1. Introducción
Cuando trabajas con archivos y carpetas, a menudo necesitas seleccionar solo determinados archivos: por ejemplo, todos los archivos ".java", todas las imágenes, todos los logs de una fecha concreta. Para ello en Java (y no solo) se usan el globbing (glob) y las expresiones regulares (regex).
- Globbing — una manera sencilla de describir un patrón de nombre de archivo mediante caracteres especiales (*, ?, [], {}), como en la línea de comandos de Linux o Windows.
- Regex — un lenguaje potente de expresiones regulares para patrones complejos.
Ejemplos de patrones glob
- *.java — todos los archivos con la extensión ".java" en la carpeta actual.
- **/*.java — todos los archivos ".java" en todas las subcarpetas (doble asterisco — búsqueda recursiva).
- *.{png,jpg} — todos los archivos con extensión ".png" o ".jpg".
- file-??.log — archivos como "file-01.log", "file-AB.log" (dos caracteres cualesquiera).
- [A-Z]*.txt — todos los archivos ".txt" que empiezan por una mayúscula.
El globbing es más simple que regex y la mayoría de las veces basta para filtrar archivos.
Comparación de glob y regex
| Característica | glob | regex |
|---|---|---|
| Simplicidad | Muy simple | Más complejo, pero más potente |
| Símbolos | |
Todas las posibilidades de regex |
| Ejemplos | |
|
| Recursividad | |
Sin compatibilidad integrada |
| Cuándo usar | Filtrado de archivos | Comprobaciones complejas |
2. PathMatcher: filtrado de archivos por patrón
En Java NIO (java.nio.file) para filtrar archivos por patrón se utiliza la interfaz PathMatcher. Se puede obtener a través de FileSystems.getDefault().getPathMatcher(...).
Sintaxis
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
- "glob:*.java" — patrón de globbing.
- "regex:.*\\.java" — patrón de expresión regular.
Ejemplo: filtrado de archivos en una carpeta
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 comprueba solo el nombre del archivo — pasa entry.getFileName(), no la ruta completa.
- Para búsqueda recursiva usa Files.walk() o Files.find().
Files.walk() — método de NIO2 que devuelve un Stream<Path> con todos los archivos y carpetas en el directorio indicado de forma recursiva, incluidas las subcarpetas. A diferencia de DirectoryStream, que muestra solo el contenido de una carpeta, Files.walk() permite trabajar con el árbol de directorios mediante la Stream API.
Ejemplo de uso:
import java.nio.file.*;
import java.util.stream.Stream;
Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // todas las subcarpetas de forma recursiva
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Ejemplo con regex
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");
3. Files.newDirectoryStream: filtrado al listar una carpeta
El método Files.newDirectoryStream() permite filtrar archivos por patrón o mediante un filtro propio.
Filtrado por patrón glob
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
El segundo parámetro es un patrón glob (sin el prefijo "glob:").
Filtrado mediante DirectoryStream.Filter
Si necesitas una lógica más compleja (por ejemplo, filtrar por tamaño, fecha, excluir carpetas), usa DirectoryStream.Filter<Path>:
DirectoryStream.Filter<Path> filter = path -> {
// Ejemplo: solo archivos, no carpetas, y ni .git ni 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: selección potente con BiPredicate
Si necesitas buscar archivos con condiciones complejas (por ejemplo, por fecha, tamaño, nombre, de forma recursiva), usa Files.find.
Sintaxis
Stream<Path> stream = Files.find(
startDir, // dónde buscar
maxDepth, // profundidad (Integer.MAX_VALUE — recursivo)
(path, attrs) -> {
// path — ruta al archivo
// attrs — atributos del archivo (tamaño, fecha, etc.)
return path.getFileName().toString().endsWith(".log")
&& attrs.size() > 1024; // solo logs grandes
}
);
stream.forEach(System.out::println);
- El segundo parámetro es la profundidad máxima de búsqueda.
- El tercero es BiPredicate<Path, BasicFileAttributes>: devuelve true si el archivo coincide.
Ejemplo: excluir las carpetas .git y node_modules
Stream<Path> stream = Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
// Excluimos las carpetas .git y node_modules
if (name.equals(".git") || name.equals("node_modules")) return false;
// Solo archivos .log de más de 1 MB
return name.endsWith(".log") && attrs.size() > 1024 * 1024;
}
);
stream.forEach(System.out::println);
5. Práctica
Ejemplo 1: Encontrar todos los archivos .log, salvo .git y node_modules
Files.walk(Paths.get("."))
.filter(path -> {
String name = path.getFileName().toString();
// Excluimos las carpetas
if (name.equals(".git") || name.equals("node_modules")) return false;
// Solo archivos .log
return name.endsWith(".log");
})
.forEach(System.out::println);
Ejemplo 2: Encontrar todas las imágenes creadas después de una fecha determinada
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);
Ejemplo 3: Encontrar todos los archivos grandes (más de 10 MB), excepto .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. Detalles útiles
Chuleta breve de la sintaxis de glob
- * — cualquier cantidad de cualquier carácter (excepto el separador /)
- ** — cualquier número de carpetas (funciona en Java)
- ? — exactamente un carácter cualquiera
- [abc] — cualquiera de los caracteres a, b, c
- [a-z] — cualquier carácter del rango
- {a,b,c} — cualquiera de los valores enumerados (por ejemplo, *.{png,jpg})
Ejemplos:
- *.java — todos los archivos ".java" en la carpeta actual
- **/*.java — todos los archivos ".java" en todas las subcarpetas
- file-??.log — archivos como "file-01.log", "file-AB.log"
- [A-Z]*.txt — todos los archivos ".txt" que empiezan por una mayúscula
Rendimiento y fugas: ¡cierra DirectoryStream!
¡Importante!
- DirectoryStream y los streams de Files.find/Files.walk son recursos que hay que cerrar.
- Usa try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
// ...
}
}
- Si no cierras el stream, puede producirse una fuga de recursos (por ejemplo, «demasiados archivos abiertos»).
- Para Files.find y Files.walk, llama obligatoriamente a close() o usa try-with-resources:
try (Stream<Path> stream = Files.find(...)) {
stream.forEach(System.out::println);
}
7. Conclusiones y errores típicos
Error n.º 1: Usar un patrón glob sin entender que * no busca de forma recursiva. Para recursión usa "**/*.java" o Files.walk.
Error n.º 2: Pasar la ruta completa a matcher.matches() — normalmente hay que pasar solo el nombre del archivo (getFileName()).
Error n.º 3: Olvidar cerrar DirectoryStream o Stream<Path> — acabarás con una fuga de recursos.
Error n.º 4: Patrones de regex demasiado complejos para un filtrado simple — usa glob si es suficiente.
Error n.º 5: No excluir carpetas de sistema (".git", "node_modules") — la búsqueda se vuelve lenta y se «ensucia» con archivos innecesarios.
GO TO FULL VERSION