Volver a Java Intermedio

Introducción

Las operaciones de entrada/salida de archivos son esenciales para leer y escribir datos en archivos. Java proporciona varias clases para operaciones de archivos con diferentes niveles de abstracción.

Lectura de Archivos

BufferedReader (línea por línea):

try (BufferedReader reader = new BufferedReader(new FileReader("datos.txt"))) { String linea; while ((linea = reader.readLine()) != null) { System.out.println(linea); } } catch (IOException e) { System.err.println("Error leyendo archivo: " + e.getMessage()); }

Files.readAllLines (archivo completo):

try { List<String> lineas = Files.readAllLines(Paths.get("datos.txt")); lineas.forEach(System.out::println); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); }

Files.readString (como string único):

try { String contenido = Files.readString(Paths.get("datos.txt")); System.out.println(contenido); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); }

Escritura de Archivos

BufferedWriter:

try (BufferedWriter writer = new BufferedWriter(new FileWriter("salida.txt"))) { writer.write("¡Hola, Mundo!"); writer.newLine(); writer.write("Segunda línea"); } catch (IOException e) { System.err.println("Error escribiendo archivo: " + e.getMessage()); }

Files.write:

List<String> lineas = Arrays.asList("Línea 1", "Línea 2", "Línea 3"); try { Files.write(Paths.get("salida.txt"), lineas); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); }

Files.writeString:

try { Files.writeString(Paths.get("salida.txt"), "¡Hola, Mundo!"); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); }

Operaciones de Archivos

Verificar si existe el archivo:

Path ruta = Paths.get("datos.txt"); if (Files.exists(ruta)) { System.out.println("El archivo existe"); }

Crear directorios:

Path dir = Paths.get("logs/errores"); Files.createDirectories(dir); // Crea todos los directorios faltantes

Eliminar archivos:

Files.deleteIfExists(Paths.get("temp.txt"));

Copiar archivos:

Files.copy( Paths.get("origen.txt"), Paths.get("destino.txt"), StandardCopyOption.REPLACE_EXISTING );

Mover archivos:

Files.move( Paths.get("viejo.txt"), Paths.get("nuevo.txt"), StandardCopyOption.REPLACE_EXISTING );

Trabajando con Directorios

Listar archivos en directorio:

try (Stream<Path> archivos = Files.list(Paths.get("."))) { archivos.filter(Files::isRegularFile) .forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }

Recorrer árbol de directorios:

try (Stream<Path> rutas = Files.walk(Paths.get("src"))) { rutas.filter(p -> p.toString().endsWith(".java")) .forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }

Archivos Binarios

Lectura de bytes:

try { byte[] bytes = Files.readAllBytes(Paths.get("imagen.png")); System.out.println("Tamaño del archivo: " + bytes.length); } catch (IOException e) { e.printStackTrace(); }

Escritura de bytes:

byte[] datos = {1, 2, 3, 4, 5}; try { Files.write(Paths.get("datos.bin"), datos); } catch (IOException e) { e.printStackTrace(); }

Archivos de Configuración

Lectura de propiedades:

Properties props = new Properties(); try (FileInputStream fis = new FileInputStream("config.properties")) { props.load(fis); String host = props.getProperty("database.host"); String puerto = props.getProperty("database.port", "3306"); // por defecto } catch (IOException e) { e.printStackTrace(); }

Escritura de propiedades:

Properties props = new Properties(); props.setProperty("database.host", "localhost"); props.setProperty("database.port", "3306"); try (FileOutputStream fos = new FileOutputStream("config.properties")) { props.store(fos, "Configuración de Base de Datos"); } catch (IOException e) { e.printStackTrace(); }

Buenas Prácticas

  • Siempre usa try-with-resources para operaciones de archivos
  • Usa la clase Files para operaciones modernas de archivos
  • Maneja IOException apropiadamente
  • Cierra streams en bloque finally (si no usas try-with-resources)
  • Usa Path en lugar de File para código nuevo
  • Especifica charset al leer/escribir texto
  • Verifica permisos de archivo antes de operaciones

Patrones Comunes

Lectura segura de archivos con valor por defecto:

public List<String> leerArchivoConfig(String ruta) { try { return Files.readAllLines(Paths.get(ruta)); } catch (IOException e) { logger.warn("Archivo de configuración no encontrado, usando valores por defecto"); return Collections.emptyList(); } }

Escritura atómica de archivos:

Path temp = Files.createTempFile("config", ".tmp"); try { Files.write(temp, contenido.getBytes()); Files.move(temp, rutaDestino, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { Files.deleteIfExists(temp); throw e; }

Ejemplo Práctico

public class GestorDatosJugador { private final Path dirDatos = Paths.get("playerdata"); public void guardarDatosJugador(UUID idJugador, String datos) { try { Files.createDirectories(dirDatos); Path archivo = dirDatos.resolve(idJugador.toString() + ".json"); Files.writeString(archivo, datos, StandardCharsets.UTF_8); } catch (IOException e) { logger.error("Error al guardar datos del jugador: " + e.getMessage()); } } public Optional<String> cargarDatosJugador(UUID idJugador) { Path archivo = dirDatos.resolve(idJugador.toString() + ".json"); try { if (Files.exists(archivo)) { return Optional.of(Files.readString(archivo, StandardCharsets.UTF_8)); } } catch (IOException e) { logger.error("Error al cargar datos del jugador: " + e.getMessage()); } return Optional.empty(); } public List<UUID> obtenerTodosLosIdsJugadores() { try (Stream<Path> archivos = Files.list(dirDatos)) { return archivos.filter(p -> p.toString().endsWith(".json")) .map(p -> p.getFileName().toString()) .map(nombre -> nombre.replace(".json", "")) .map(UUID::fromString) .collect(Collectors.toList()); } catch (IOException e) { logger.error("Error al listar archivos de jugadores"); return Collections.emptyList(); } } }

Conclusión

Las operaciones de E/S de archivos son fundamentales para persistir datos. Usar APIs modernas como Files y Path hace las operaciones de archivos más seguras y legibles.

En el próximo capítulo, exploraremos conceptos de programación funcional en Java.