1. Recorrer archivos en un directorio
Obtener la lista de archivos: Files.list y Files.walk
En Java hay dos métodos muy útiles para trabajar con el contenido de una carpeta:
- Files.list(Path) — devuelve un flujo (Stream<Path>) de todos los archivos y carpetas del nivel superior en el directorio indicado.
- Files.walk(Path) — devuelve un flujo de todos los archivos y carpetas en el directorio y en todas sus subcarpetas (de forma recursiva).
Ejemplo: mostrar todos los archivos y carpetas en el directorio data
import java.nio.file.*;
import java.io.IOException;
public class ListFilesExample {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("data");
// Obtenemos un flujo de todos los archivos y carpetas del nivel superior
try (var stream = Files.list(dir)) {
stream.forEach(System.out::println);
}
}
}
Si quieres recorrer todas las carpetas anidadas (recursivamente), usa Files.walk:
try (var stream = Files.walk(dir)) {
stream.forEach(System.out::println);
}
Atención:
Files.walk puede devolver muchísimos archivos si la estructura es grande — no lo ejecutes en la raíz del disco si no quieres ver «la lista del universo entero»!
Visualización: diferencia entre list y walk
| Método | Qué devuelve |
|---|---|
|
Solo el contenido de la carpeta actual |
|
Todos los archivos y carpetas, incluidas las anidadas |
2. Filtrado de archivos
Una de las principales ventajas de trabajar con streams (Stream<Path>) es la posibilidad de filtrar archivos por cualquier criterio: extensión, nombre, tamaño, fecha de creación, etc.
Filtrado por extensión
Supongamos que solo necesitamos archivos .txt. Podemos filtrarlos así:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Filtrado por nombre
Queremos todos los archivos cuyo nombre empiece por "report":
stream
.filter(path -> path.getFileName().toString().startsWith("report"))
.forEach(System.out::println);
Filtrado por tamaño
Obtendremos solo los archivos «pesados» (más de 1 MB):
stream
.filter(path -> {
try {
return Files.size(path) > 1_000_000;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Filtrado por fecha
Por ejemplo, archivos modificados en los últimos 7 días:
import java.time.*;
import java.nio.file.attribute.*;
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isAfter(Instant.now().minus(Duration.ofDays(7)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Todo junto: filtrado combinado
Puedes combinar filtros «como en un restaurante»: por ejemplo, todos los archivos .log de hace más de un mes y de más de 10 KB:
stream
.filter(path -> path.toString().endsWith(".log"))
.filter(path -> {
try {
return Files.size(path) > 10_000;
} catch (IOException e) {
return false;
}
})
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(30)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
3. Copia y eliminación masivas de archivos
Las operaciones masivas son sencillas: obtienes un flujo de archivos y aplicas la función necesaria a cada elemento. Lo principal — no olvidar los posibles errores (por ejemplo, archivo en uso o ya existente).
Copiar todos los archivos de una carpeta a otra
import java.nio.file.StandardCopyOption;
Path sourceDir = Paths.get("data");
Path targetDir = Paths.get("backup");
try (var stream = Files.list(sourceDir)) {
stream.forEach(path -> {
Path targetPath = targetDir.resolve(path.getFileName());
try {
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiado: " + path.getFileName());
} catch (IOException e) {
System.out.println("Error al copiar " + path.getFileName() + ": " + e.getMessage());
}
});
}
Eliminar todos los archivos temporales (*.tmp)
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminado: " + path.getFileName());
} catch (IOException e) {
System.out.println("Error al eliminar " + path.getFileName() + ": " + e.getMessage());
}
});
}
Uso de Stream API para procesar colecciones de rutas
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.sorted((p1, p2) -> {
try {
return Long.compare(Files.size(p2), Files.size(p1)); // por tamaño, descendente
} catch (IOException e) {
return 0;
}
})
.limit(5) // solo los 5 más grandes
.forEach(System.out::println);
}
4. Tareas prácticas
Buscar y eliminar archivos anteriores a una fecha determinada
Eliminemos todos los archivos que no se han modificado en más de un año:
try (var stream = Files.list(dir)) {
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(365)));
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminado: " + path.getFileName());
} catch (IOException e) {
System.out.println("Error al eliminar " + path.getFileName() + ": " + e.getMessage());
}
});
}
Renombrado masivo de archivos según un patrón
Supongamos que hay que añadir el prefijo "old_" a cada archivo .txt:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
Path newPath = path.resolveSibling("old_" + path.getFileName());
try {
Files.move(path, newPath);
System.out.println("Renombrado: " + path.getFileName() + " -> " + newPath.getFileName());
} catch (IOException e) {
System.out.println("Error al renombrar " + path.getFileName() + ": " + e.getMessage());
}
});
}
Copiar todos los archivos de todas las subcarpetas (recursivo)
Usamos Files.walk para recorrer todos los archivos en todas las subcarpetas:
try (var stream = Files.walk(sourceDir)) {
stream
.filter(Files::isRegularFile)
.forEach(path -> {
Path relative = sourceDir.relativize(path);
Path targetPath = targetDir.resolve(relative);
try {
Files.createDirectories(targetPath.getParent());
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiado: " + path + " -> " + targetPath);
} catch (IOException e) {
System.out.println("Error al copiar " + path + ": " + e.getMessage());
}
});
}
5. Aspectos importantes y particularidades
Gestión de errores en operaciones masivas
En las operaciones masivas casi siempre hay archivos «problemáticos»: sin acceso, archivo en uso, ruta demasiado larga, archivo ya existente, etc. Si no gestionas las excepciones dentro del bucle, el programa puede fallar en el primer error. Por eso, usa siempre try-catch dentro de forEach para «tragar» el error de un archivo y poder procesar el resto.
Impacto de la recursión en el rendimiento y la pila de llamadas
Al usar Files.walk para directorios grandes, se puede obtener un recorrido muy largo si haces recursión manualmente. Por suerte, el propio método está implementado de forma eficiente y no provoca desbordamiento de pila. Pero si escribes tu propia función recursiva para eliminar/copiar directorios, sé prudente: para estructuras con enormes niveles de anidamiento la pila puede «reventar».
Trabajo con carpetas grandes
- Usa Files.walk con precaución: si la carpeta contiene miles de archivos, puede llevar tiempo y consumir mucha memoria.
- Si solo necesitas procesar el nivel superior, usa Files.list.
- Para buscar archivos por máscara, utiliza el filtrado por nombre (endsWith, matches, etc.).
Ejemplo: «Limpiar una carpeta de archivos temporales»
Path tempDir = Paths.get("data/tmp");
try (var stream = Files.walk(tempDir)) {
stream
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminado: " + path);
} catch (IOException e) {
System.out.println("Error al eliminar " + path + ": " + e.getMessage());
}
});
}
Métodos principales para operaciones masivas
| Operación | Método/enfoque | Particularidades |
|---|---|---|
| Obtener todos los archivos | |
walk — recursivo, list — solo el nivel superior |
| Filtrado por extensión | |
Se puede combinar con otros filtros |
| Copiar un grupo de archivos | |
Comprueba la existencia de la carpeta de destino |
| Eliminar un grupo de archivos | |
Mejor usar try-catch para cada operación |
| Renombrado masivo | |
Renombrar es un move con un nombre nuevo |
6. Errores típicos en operaciones masivas
Error n.º 1: Excepciones no gestionadas dentro de forEach.
Un problema muy común: si no envuelves cada operación en try-catch, el programa fallará en el primer archivo problemático y los demás no se procesarán.
Error n.º 2: Intentar eliminar/copiar una carpeta como si fuera un archivo (o al revés).
Los métodos Files.delete y Files.copy tratan archivos y carpetas de forma distinta. ¡No los confundas! Por ejemplo, intentar eliminar una carpeta no vacía con el método estándar provocará un error.
Error n.º 3: Formación incorrecta de la ruta de destino al copiar.
Si creas la ruta de destino sin tener en cuenta la estructura (por ejemplo, sin usar relativize), puedes sobrescribir archivos o acabar con una estructura incorrecta en el destino.
Error n.º 4: Abrir demasiados archivos a la vez.
Si abres streams para lectura/escritura dentro del bucle, ¡no olvides cerrarlos! Es mejor usar try-with-resources.
Error n.º 5: Recorrido recursivo sin limitar la profundidad.
Al usar Files.walk en carpetas muy grandes y profundas, puedes tener problemas de rendimiento y memoria. Si necesitas limitar la profundidad, usa la sobrecarga con parámetro de profundidad: Files.walk(dir, depth).
Error n.º 6: Ignorar de forma implícita archivos ocultos/sistema.
Si tu aplicación trabaja con archivos de usuario, podrías tocar accidentalmente archivos ocultos o del sistema. Para filtrarlos utiliza Files.isHidden(path).
GO TO FULL VERSION