CodeGym /Cursos /JAVA 25 SELF /Globbing/PathMatcher, DirectoryStream.Filter

Globbing/PathMatcher, DirectoryStream.Filter

JAVA 25 SELF
Nivel 39 , Lección 2
Disponible

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
*.java
.*\.java
Recursividad
**/*.java
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.

1
Tarea
JAVA 25 SELF, nivel 39, lección 2
Bloqueada
Analista de datos: selección de informes de importancia crítica
Analista de datos: selección de informes de importancia crítica
1
Tarea
JAVA 25 SELF, nivel 39, lección 2
Bloqueada
Curador de la galería: inventario de imágenes
Curador de la galería: inventario de imágenes
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION