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
Filespara operaciones modernas de archivos - Maneja
IOExceptionapropiadamente - Cierra streams en bloque finally (si no usas try-with-resources)
- Usa
Pathen lugar deFilepara 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.