Java 17 · NIO.2 · Streams API · MD5 Hashing

JDiskCleaner Pro

Memoria técnica completa del desarrollo de un sistema modular de análisis y optimización de sistemas de archivos. Implementado en Java 17 con programación funcional, hashing criptográfico y gestión avanzada de errores.

5
Clases Core
8+
Funcionalidades
MD5
Dedup Hash
NIO.2
I/O API
Java 17
Runtime
Sección 01

Motivación y Contexto

¿Por qué construir JDiskCleaner? El punto de partida del proyecto.

El Problema

La acumulación de archivos temporales, duplicados y datos obsoletos es un problema crítico en sistemas de desarrollo y producción. Los gestores de disco convencionales son cajas negras: el usuario no sabe qué están borrando ni por qué.

La Solución

Una librería de consola con filosofía auditora: el usuario comprende en todo momento qué acción se está ejecutando, qué archivos se ven afectados y con qué criterio. Transparencia total sobre el sistema de archivos.

Filosofía de Diseño

Arquitectura en capas con Responsabilidad Única (SRP). Cada clase tiene un único propósito y puede ser reutilizada de forma independiente. El motor DiskService es completamente desacoplado de la UI.

Objetivos de Aprendizaje

Dominar la API Java NIO.2 para I/O moderno, aplicar la Streams API para procesamiento funcional de datos, y gestionar errores mediante excepciones personalizadas con jerarquías bien definidas.

Sección 02

Arquitectura del Proyecto

Estructura en capas desacopladas siguiendo principios SOLID.

UI Layer
📟 Main.java
🖥️ MenuController
↓ invoca
Service
⚙️ DiskService.java
🔍 DiskScanner
🗂️ FileOrganizer
↓ usa
Utils
🔧 FileUtil.java
#️⃣ HashUtil (MD5)
📏 SizeFormatter
↓ retorna
Model
📦 FileStats.java (Record)
🗄️ DuplicateGroup
Exception
❌ DiskException.java
🚫 AccessDeniedException

Conceptos Java Aplicados

Java Streams API
NIO.2 (Files, Path)
Java Records (Java 16+)
MD5 MessageDigest
Custom Exceptions
Lambdas & Method Refs
Comparator Chaining
Collectors.groupingBy
try-with-resources
Predicate Composition
Sección 03

Flujo de Ejecución

Cómo viajan los datos desde el input del usuario hasta el resultado en pantalla.

📟
Main.java
Scanner input
Validación
Path.exists()
⚙️
DiskService
Lógica core
🚶
Files.walk()
NIO.2 Traverse
🔀
Stream Ops
filter/map/sort
📦
FileStats
Record inmutable
📊
Output
Resultado UI
Sección 04

Implementación: Código Real

Explora cada fichero del proyecto con análisis técnico línea a línea.

Java 17 · JDiskCleanerPro
JDiskCleanerPro / src / com / jdisk / ui / Main.java
Main.java
Java
— lines
main
UTF-8
Análisis Técnico Detallado
Sección 05

Catálogo de Funcionalidades

Desglose técnico de cada operación implementada en el sistema.

Funcionalidad Categoría Implementación Técnica Clases Involucradas Impacto
Análisis Top-N Archivos
Mayores consumidores de espacio
Diagnóstico Recorrido recursivo con Files.walk(). Pipeline de Stream con filtermapsorted(reversed)limit(N). DiskService, FileStats Identificación de archivos pesados sin explorar manualmente.
Detección de Duplicados
Comparación por contenido binario
Avanzado Cálculo de hash MD5 con MessageDigest. Agrupación con Collectors.groupingBy(hash). Solo los grupos con size() > 1 son duplicados reales. FileUtil, DiskService, DuplicateGroup Ahorro de espacio real basado en contenido, no en nombres.
Limpieza de Temporales
Extensiones: .tmp, .log, .bak, .cache
Limpieza Filtros de extensión dinámicos con String.endsWith() en predicados de Stream. Eliminación con Files.delete() precedida de confirmación. DiskService, FileUtil Optimización rápida del disco con mínimo riesgo.
Organizador por Extensión
Carpetas: Images, Docs, Videos...
Organización Detección de extensión con Path.getFileName(). Creación dinámica de carpetas con Files.createDirectories(). Movimiento con Files.move(). DiskService, FileOrganizer Estructura de directorios limpia y auto-gestionada.
Reporte de Espacio
Resumen de uso por directorio
Diagnóstico Suma acumulada de bytes con Stream.mapToLong().sum(). Formateo de unidades (B/KB/MB/GB) con lógica condicional encadenada. DiskService, FileUtil Visión general del estado del disco instantánea.
Búsqueda por Patrón
Glob patterns y wildcards
Avanzado Uso de FileSystem.getPathMatcher() con sintaxis glob. Pipeline de Stream con filter(matcher::matches). DiskService, FileUtil Localización precisa de archivos por patrones complejos.
Archivos por Antigüedad
Filtro por fecha de último acceso
Limpieza Lectura de BasicFileAttributes con Files.readAttributes(). Comparación de FileTime con umbral configurable. DiskService, FileStats Eliminación segura de archivos inactivos.
Exportar Informe
Salida en formato .txt estructurado
Utilidad Escritura con Files.write() y StandardOpenOption.CREATE. Construcción del contenido con StringBuilder y formateo de fechas con LocalDateTime. DiskService, FileUtil Trazabilidad completa de todas las operaciones realizadas.
Sección 06

Conceptos Java en Profundidad

Los pilares técnicos del proyecto explicados didácticamente.

Java Streams API

Los Streams permiten procesar colecciones de datos de forma declarativa, sin bucles explícitos. Se componen de operaciones intermedias (filter, map, sorted) y terminales (collect, toList). Son lazy: no se evalúan hasta la operación terminal.

Files.walk(root)
.filter(Files::isRegularFile)
.sorted(...reversed())
.limit(10).toList()

Java NIO.2

La API de I/O moderna de Java (desde Java 7). Reemplaza a java.io.File con java.nio.file.Path. Ofrece Files.walk() para recorrido recursivo, Files.readAttributes() para metadatos, y Files.move/delete/copy para operaciones atómicas.

Path p = Paths.get("/home");
BasicFileAttributes attrs =
Files.readAttributes(p,
BasicFileAttributes.class);

Hashing MD5

Para detectar duplicados reales (no por nombre), se calcula el hash MD5 de cada archivo. Dos archivos con el mismo hash tienen contenido idéntico con probabilidad criptográfica. Se usa MessageDigest de java.security.

MessageDigest md =
MessageDigest.getInstance("MD5");
byte[] hash = md.digest(
Files.readAllBytes(path));

Java Records (Java 16+)

Los Records son clases de datos inmutables con sintaxis concisa. El compilador genera automáticamente constructor, getters, equals(), hashCode() y toString(). Perfectos para transportar resultados de análisis sin riesgo de mutación accidental.

public record FileStats(
String name,
long size,
String path
) { }

Excepciones Personalizadas

En lugar de propagar genéricas IOException, JDiskCleaner usa una jerarquía propia. DiskException extiende Exception y diferencia fallos de acceso de errores lógicos. Esto permite catch específicos y mensajes de error claros para el usuario.

} catch (DiskException e) {
System.err.println(
"Error: " + e.getMessage());
}

Try-with-Resources

Los Streams de NIO.2 como el que devuelve Files.walk() implementan AutoCloseable. Usarlos en un bloque try(Stream<Path> s = Files.walk(...)) garantiza que el stream se cierra automáticamente, evitando file handle leaks.

try (Stream<Path> stream =
Files.walk(root)) {
return stream.filter(...)
.toList();
}
Sección 07

Evolución del Desarrollo

Proceso iterativo desde el esqueleto hasta la versión funcional.

Fase 1 — Setup
Estructura del Proyecto
Creación de packages (ui, service, util, model, exception). Definición de contratos entre capas. Primer esqueleto de Main.java con menú básico.
Fase 2 — Core
Motor de Análisis
Implementación de Files.walk() con manejo de permisos. Construcción del pipeline de Streams para el análisis Top-N. Definición del Record FileStats.
Fase 3 — Features
Funcionalidades Avanzadas
Integración del hashing MD5 para duplicados. Implementación del organizador de archivos por extensión. Añadido el filtro de temporales con extensiones configurables.
Fase 4 — Polish
Refinamiento y Errores
Implementación de DiskException y jerarquía de errores. Mejora del formateo de salida con colores ANSI. Exportación de informes en texto. Pruebas en directorios reales.

Decisiones de Diseño Clave

  • ¿Por qué Records para FileStats? La inmutabilidad garantiza que los resultados del análisis no se corrompan al ser pasados entre capas.
  • ¿Por qué MD5 y no SHA-256? MD5 es suficiente para detectar duplicados (no es un contexto de seguridad) y es significativamente más rápido.
  • ¿Por qué try-with-resources en Files.walk? Los streams de NIO.2 son AutoCloseable. Sin cerrarlos explícitamente se generan file descriptor leaks.
  • ¿Por qué DiskService no extiende nada? Se prefirió composición sobre herencia. La clase es final y sus métodos son estáticos donde aplica.
  • Sección 08

    Análisis Crítico

    Una evaluación honesta de las fortalezas y limitaciones del sistema.

    Fortalezas Técnicas

    • Records de Java 16+ garantizan inmutabilidad sin boilerplate de getters/setters.
    • Streams lazy evalúan solo los datos necesarios, evitando cargar miles de objetos en memoria.
    • Detección de duplicados por contenido (MD5) es infalible ante renombrados.
    • Arquitectura en capas permite integrar DiskService en otras apps Java sin modificaciones.
    • Try-with-resources previene file handle leaks en iteraciones largas.
    • Excepciones personalizadas dan mensajes de error comprensibles al usuario final.

    Limitaciones Conocidas

    • El escaneo es IO-Bound: la velocidad depende del hardware (HDD vs SSD).
    • Archivos bloqueados por el SO requieren elevación de privilegios no implementada.
    • MD5 carga el archivo completo en memoria; archivos muy grandes (>2GB) pueden causar OOM.
    • No hay paralelización: podría mejorarse con parallelStream() en cores múltiples.
    • La interfaz de consola limita la usabilidad para usuarios no técnicos.
    • Sin persistencia: cada ejecución hace el análisis desde cero sin caché.

    Posibles Mejoras Futuras

    Implementar Files.walkFileTree() con un SimpleFileVisitor personalizado para mayor control de acceso. Añadir parallel streams para discos SSD con múltiples cores. Migrar la UI a JavaFX con gráficas de uso de disco. Implementar un caché con ObjectOutputStream para evitar re-escaneos completos.

    Sección 09

    Declaración de Uso de LLM / IA

    Transparencia total sobre el uso de herramientas de inteligencia artificial.

    ¿Cómo se usó ChatGPT?

    • Refactorización de pipelines: Optimización del orden de operadores en Streams para máxima eficiencia (filter antes de map).
    • Formateo de unidades: Sugerencia del patrón condicional para la conversión de bytes a KB/MB/GB de forma legible.
    • Estructura de excepciones: Orientación sobre jerarquías de excepciones checked vs unchecked para este tipo de proyecto.
    • API de Hashing: Referencia a MessageDigest y la forma correcta de convertir el array de bytes a hexadecimal.
    • Documentación: Estructuración del caso de estudio en este formato web interactivo.

    Compromiso de Integridad

    Todo el código generado o sugerido por IA ha sido:

    Revisado línea a línea
    Comprendido conceptualmente antes de integrar
    Verificado mediante pruebas en directorios reales
    Adaptado a la arquitectura específica del proyecto
    Documentado con comentarios propios