Conceptos básicos: flujos, archivos, directorios
El sistema de entrada y salida (I/O) en Java permite a las aplicaciones interactuar con recursos externos como archivos, conexiones de red y dispositivos. Este sistema se basa en tres conceptos fundamentales: flujos para transferir datos, archivos como unidades de almacenamiento, y directorios como contenedores organizativos.
Flujos (Streams)
Los flujos son secuencias ordenadas de datos que viajan desde un origen hacia un destino. En Java, los flujos se dividen en dos categorías principales:
- Flujos de bytes: Trabajan con datos binarios (secuencias de bytes)
- Flujos de caracteres: Especializados en texto (secuencias de caracteres)
Flujos de bytes
Los flujos de bytes son ideales para manejar datos binarios como imágenes, archivos comprimidos o cualquier dato no textual. Las clases principales son:
- InputStream: Clase abstracta base para leer bytes
- OutputStream: Clase abstracta base para escribir bytes
Ejemplo básico de lectura de bytes desde un archivo:
try (FileInputStream fis = new FileInputStream("imagen.jpg")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
// Procesar los bytes leídos
System.out.println("Leídos " + bytesRead + " bytes");
}
} catch (IOException e) {
e.printStackTrace();
}
Algunas implementaciones comunes de flujos de bytes incluyen:
- FileInputStream/FileOutputStream: Para leer/escribir archivos
- BufferedInputStream/BufferedOutputStream: Añaden buffer para mejorar rendimiento
- DataInputStream/DataOutputStream: Para tipos de datos primitivos
- ByteArrayInputStream/ByteArrayOutputStream: Para trabajar con arrays de bytes
Flujos de caracteres
Los flujos de caracteres están optimizados para texto y manejan automáticamente la codificación de caracteres. Las clases principales son:
- Reader: Clase abstracta base para leer caracteres
- Writer: Clase abstracta base para escribir caracteres
Ejemplo de escritura de texto en un archivo:
try (FileWriter writer = new FileWriter("datos.txt")) {
writer.write("Esto es una línea de texto.\n");
writer.write("Esta es otra línea de texto.");
} catch (IOException e) {
e.printStackTrace();
}
Implementaciones comunes de flujos de caracteres:
- FileReader/FileWriter: Para leer/escribir archivos de texto
- BufferedReader/BufferedWriter: Añaden buffer y métodos convenientes como
readLine()
- InputStreamReader/OutputStreamWriter: Puentes entre flujos de bytes y caracteres
- StringReader/StringWriter: Para trabajar con Strings como origen/destino
Archivos
En Java, la clase File ha sido tradicionalmente la forma de representar archivos y directorios en el sistema de archivos. Desde Java 7, la API NIO.2 introdujo la interfaz Path y la clase Files que ofrecen funcionalidades más avanzadas.
Clase File
La clase File
representa una ruta a un archivo o directorio en el sistema de archivos:
// Crear una referencia a un archivo
File archivo = new File("documentos/informe.txt");
// Verificar si existe
boolean existe = archivo.exists();
// Obtener información
long tamaño = archivo.length();
boolean esArchivo = archivo.isFile();
boolean esDirectorio = archivo.isDirectory();
String nombreArchivo = archivo.getName();
String rutaAbsoluta = archivo.getAbsolutePath();
// Crear un nuevo archivo
try {
boolean creado = archivo.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// Eliminar un archivo
boolean eliminado = archivo.delete();
Path y Files (NIO.2)
La API NIO.2 proporciona la interfaz Path
y la clase utilitaria Files
que ofrecen operaciones más potentes y flexibles:
// Crear una referencia a un archivo usando Path
Path ruta = Paths.get("documentos", "informe.txt");
// Operaciones básicas usando Files
try {
// Verificar si existe
boolean existe = Files.exists(ruta);
// Leer todo el contenido de un archivo de texto
List<String> lineas = Files.readAllLines(ruta, StandardCharsets.UTF_8);
// Escribir contenido a un archivo
List<String> contenido = Arrays.asList("Línea 1", "Línea 2", "Línea 3");
Files.write(ruta, contenido, StandardCharsets.UTF_8);
// Copiar un archivo
Path destino = Paths.get("documentos/copia_informe.txt");
Files.copy(ruta, destino, StandardCopyOption.REPLACE_EXISTING);
// Mover/renombrar un archivo
Path nuevaRuta = Paths.get("documentos/nuevo_nombre.txt");
Files.move(ruta, nuevaRuta, StandardCopyOption.REPLACE_EXISTING);
// Eliminar un archivo
Files.delete(ruta);
} catch (IOException e) {
e.printStackTrace();
}
Directorios
Los directorios son contenedores que organizan archivos y otros directorios en una estructura jerárquica. Java proporciona métodos para crear, listar y manipular directorios.
Trabajando con directorios usando File
// Crear una referencia a un directorio
File directorio = new File("documentos/proyectos");
// Crear un directorio
boolean creado = directorio.mkdir();
// Crear directorios incluyendo padres si no existen
boolean creadoConPadres = directorio.mkdirs();
// Listar contenido de un directorio
File[] contenido = directorio.listFiles();
if (contenido != null) {
for (File item : contenido) {
if (item.isFile()) {
System.out.println("Archivo: " + item.getName());
} else if (item.isDirectory()) {
System.out.println("Directorio: " + item.getName());
}
}
}
// Filtrar archivos por extensión
File[] archivosTxt = directorio.listFiles((dir, name) -> name.endsWith(".txt"));
Trabajando con directorios usando NIO.2
La API NIO.2 ofrece capacidades más avanzadas para trabajar con directorios:
// Crear un directorio
Path directorio = Paths.get("documentos/proyectos");
try {
Files.createDirectory(directorio);
// Crear directorios incluyendo padres
Files.createDirectories(directorio);
// Listar contenido de un directorio
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directorio)) {
for (Path entrada : stream) {
if (Files.isDirectory(entrada)) {
System.out.println("Directorio: " + entrada.getFileName());
} else {
System.out.println("Archivo: " + entrada.getFileName());
}
}
}
// Listar con filtro (solo archivos .java)
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(directorio, "*.java")) {
for (Path entrada : stream) {
System.out.println("Archivo Java: " + entrada.getFileName());
}
}
// Recorrer un árbol de directorios
Files.walkFileTree(directorio, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println("Archivo: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.println("Directorio: " + dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
Propiedades de archivos y directorios
Tanto con la API tradicional como con NIO.2, podemos obtener y modificar atributos de archivos:
// Con File
File archivo = new File("documento.txt");
boolean puedeEscribir = archivo.canWrite();
boolean puedeEjecutar = archivo.canExecute();
long ultimaModificacion = archivo.lastModified();
// Cambiar permisos
archivo.setReadable(true);
archivo.setWritable(false);
archivo.setExecutable(true);
// Con NIO.2
Path ruta = Paths.get("documento.txt");
try {
// Obtener atributos básicos
BasicFileAttributes atributos =
Files.readAttributes(ruta, BasicFileAttributes.class);
System.out.println("Tamaño: " + atributos.size());
System.out.println("Creación: " + atributos.creationTime());
System.out.println("Último acceso: " + atributos.lastAccessTime());
System.out.println("Última modificación: " + atributos.lastModifiedTime());
// Obtener permisos (POSIX - sistemas Unix/Linux)
if (System.getProperty("os.name").toLowerCase().contains("linux") ||
System.getProperty("os.name").toLowerCase().contains("mac")) {
PosixFileAttributes posixAttr =
Files.readAttributes(ruta, PosixFileAttributes.class);
System.out.println("Permisos: " + posixAttr.permissions());
// Modificar permisos
Set<PosixFilePermission> permisos = PosixFilePermissions.fromString("rw-r--r--");
Files.setPosixFilePermissions(ruta, permisos);
}
} catch (IOException e) {
e.printStackTrace();
}
Los conceptos de flujos, archivos y directorios son la base para cualquier operación de entrada/salida en Java. Dominar estas herramientas te permitirá gestionar eficientemente el almacenamiento y transferencia de datos en tus aplicaciones.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Patrón try-with-resources para manejo seguro de recursos
El manejo adecuado de recursos como archivos, conexiones de red o bases de datos es crucial en el desarrollo de aplicaciones Java. Estos recursos utilizan memoria y conexiones del sistema que deben ser liberadas correctamente cuando ya no se necesitan, incluso si ocurren excepciones durante su uso.
Antes de Java 7, la forma tradicional de gestionar recursos implicaba bloques try-catch-finally anidados que resultaban en código complejo y propenso a errores. El patrón try-with-resources fue introducido para solucionar estos problemas.
Problema del enfoque tradicional
Veamos primero cómo se manejaban los recursos antes de Java 7:
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream("datos.txt");
bis = new BufferedInputStream(fis);
// Operaciones con los flujos
int data;
while ((data = bis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// Cierre de recursos en orden inverso
try {
if (bis != null) bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Este enfoque presenta varios problemas:
- Código verboso y difícil de mantener
- Posibilidad de fugas de recursos si olvidamos cerrar alguno
- Manejo complejo cuando hay múltiples recursos
- Si ocurre una excepción en el bloque
finally
, puede ocultar la excepción original
Solución: try-with-resources
El patrón try-with-resources simplifica drásticamente el manejo de recursos mediante una sintaxis concisa que garantiza el cierre adecuado de los recursos:
try (FileInputStream fis = new FileInputStream("datos.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// Operaciones con los flujos
int data;
while ((data = bis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
// Los recursos se cierran automáticamente al finalizar el bloque try
Requisitos para usar try-with-resources
Para que un recurso pueda utilizarse con este patrón, debe implementar la interfaz AutoCloseable o su subinterfaz Closeable:
public interface AutoCloseable {
void close() throws Exception;
}
public interface Closeable extends AutoCloseable {
void close() throws IOException;
}
La mayoría de las clases de E/S en Java ya implementan estas interfaces, incluyendo:
- Flujos de entrada/salida (InputStream, OutputStream, Reader, Writer)
- Conexiones JDBC (Connection, Statement, ResultSet)
- Canales NIO (Channel)
- Scanners y Formatters
Funcionamiento interno
El compilador de Java transforma el bloque try-with-resources en un bloque try-finally tradicional, pero con manejo especial para:
- Cerrar los recursos en el orden inverso al que fueron declarados
- Suprimir excepciones secundarias que puedan ocurrir durante el cierre
- Preservar la excepción original para facilitar la depuración
Ejemplos prácticos
Lectura de un archivo de texto
try (BufferedReader reader = new BufferedReader(new FileReader("config.txt"))) {
String linea;
while ((linea = reader.readLine()) != null) {
System.out.println(linea);
}
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
Escritura en un archivo
try (FileWriter writer = new FileWriter("salida.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
bufferedWriter.write("Primera línea de texto");
bufferedWriter.newLine();
bufferedWriter.write("Segunda línea de texto");
} catch (IOException e) {
System.err.println("Error al escribir en el archivo: " + e.getMessage());
}
Copia de archivos con NIO.2
try (InputStream in = Files.newInputStream(Paths.get("origen.dat"));
OutputStream out = Files.newOutputStream(Paths.get("destino.dat"))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
System.err.println("Error durante la copia: " + e.getMessage());
}
Conexión a base de datos
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM usuarios WHERE id = ?")) {
stmt.setInt(1, 123);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println("Usuario: " + rs.getString("nombre"));
}
}
} catch (SQLException e) {
System.err.println("Error de base de datos: " + e.getMessage());
}
Manejo de excepciones suprimidas
Cuando ocurre una excepción en el bloque try y otra durante el cierre de recursos, Java preserva la excepción original y adjunta las demás como excepciones suprimidas:
try (MiRecursoProblematico recurso = new MiRecursoProblematico()) {
throw new RuntimeException("Excepción en el bloque try");
} catch (Exception e) {
System.err.println("Excepción principal: " + e.getMessage());
// Acceder a las excepciones suprimidas
Throwable[] suprimidas = e.getSuppressed();
for (Throwable suprimida : suprimidas) {
System.err.println("Excepción suprimida: " + suprimida.getMessage());
}
}
Creación de recursos personalizados compatibles
Podemos crear nuestras propias clases compatibles con try-with-resources implementando AutoCloseable:
public class ConexionPersonalizada implements AutoCloseable {
private final String nombre;
public ConexionPersonalizada(String nombre) {
this.nombre = nombre;
System.out.println("Abriendo conexión: " + nombre);
}
public void enviarDatos(String datos) {
System.out.println("Enviando: " + datos);
}
@Override
public void close() throws Exception {
System.out.println("Cerrando conexión: " + nombre);
// Lógica de limpieza de recursos
}
}
Uso de nuestro recurso personalizado:
try (ConexionPersonalizada conexion = new ConexionPersonalizada("Servidor-A")) {
conexion.enviarDatos("Hola mundo");
// La conexión se cerrará automáticamente al salir del bloque
}
Declaración de recursos fuera del bloque try
Desde Java 9, es posible utilizar variables finales o efectivamente finales declaradas fuera del bloque try:
// Java 9+
final BufferedReader reader1 = new BufferedReader(new FileReader("archivo1.txt"));
BufferedReader reader2 = new BufferedReader(new FileReader("archivo2.txt")); // Efectivamente final
try (reader1; reader2) {
// Usar los recursos
String linea1 = reader1.readLine();
String linea2 = reader2.readLine();
System.out.println(linea1 + " - " + linea2);
} catch (IOException e) {
e.printStackTrace();
}
Mejores prácticas
- Ordena los recursos de manera que los dependientes se declaren después de sus dependencias
- Evita operaciones complejas dentro de la declaración de recursos
- No realices operaciones en los recursos después del bloque try-with-resources
- Implementa AutoCloseable en tus clases que gestionen recursos externos
- Documenta el comportamiento del método
close()
en tus clases personalizadas - Captura excepciones específicas en lugar de excepciones genéricas
El patrón try-with-resources es una mejora significativa en la gestión de recursos en Java, que simplifica el código, reduce errores y mejora la robustez de las aplicaciones. Su uso es considerado una buena práctica en cualquier código que trabaje con recursos externos que deban ser liberados.
Manejo de excepciones de I/O básicas a alto nivel
El manejo efectivo de excepciones es una parte fundamental del desarrollo de aplicaciones Java robustas, especialmente cuando se trabaja con operaciones de entrada/salida (I/O). Las operaciones I/O son propensas a fallos debido a factores externos como permisos insuficientes, archivos inexistentes o problemas de hardware.
Java proporciona un sistema jerárquico de excepciones para operaciones I/O que permite manejar errores desde un nivel básico hasta estrategias más sofisticadas. Vamos a explorar este sistema y las mejores prácticas para implementarlo.
Jerarquía de excepciones I/O
La base de todas las excepciones de I/O en Java es la clase IOException
, que extiende de Exception
. Esta jerarquía incluye:
- IOException: Excepción base para todos los errores de I/O
- FileNotFoundException: El archivo especificado no existe
- EOFException: Se alcanzó el final del archivo inesperadamente
- MalformedURLException: URL con formato incorrecto
- SocketException: Error en operaciones de socket
- ConnectException: Error al conectar a un host remoto
- UnknownHostException: No se puede resolver el nombre del host
Entender esta jerarquía es esencial para implementar estrategias de manejo de excepciones efectivas.
Manejo básico de excepciones I/O
El enfoque más simple para manejar excepciones I/O es capturarlas y proporcionar un mensaje de error:
public void leerArchivo(String ruta) {
try {
FileReader fileReader = new FileReader(ruta);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String linea;
while ((linea = bufferedReader.readLine()) != null) {
System.out.println(linea);
}
bufferedReader.close();
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
}
Este enfoque tiene varias limitaciones:
- No distingue entre diferentes tipos de errores
- No garantiza el cierre de recursos
- No permite recuperación específica según el tipo de error
Manejo intermedio: captura específica
Un enfoque más refinado es capturar excepciones específicas para proporcionar respuestas adaptadas a cada tipo de error:
public void leerArchivo(String ruta) {
BufferedReader bufferedReader = null;
try {
FileReader fileReader = new FileReader(ruta);
bufferedReader = new BufferedReader(fileReader);
String linea;
while ((linea = bufferedReader.readLine()) != null) {
System.out.println(linea);
}
} catch (FileNotFoundException e) {
System.err.println("El archivo no existe: " + ruta);
System.err.println("Detalles: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error de lectura en el archivo: " + ruta);
System.err.println("Detalles: " + e.getMessage());
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
System.err.println("Error al cerrar el archivo: " + e.getMessage());
}
}
}
}
Este enfoque mejora el manejo de errores al:
- Distinguir entre archivo no encontrado y otros errores de I/O
- Garantizar el cierre de recursos mediante el bloque
finally
- Proporcionar mensajes de error más específicos
Manejo avanzado: propagación controlada
En aplicaciones más complejas, a menudo es mejor propagar las excepciones a capas superiores donde se puede tomar una decisión más informada:
public List<String> leerLineasArchivo(String ruta) throws IOException {
List<String> lineas = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(ruta))) {
String linea;
while ((linea = reader.readLine()) != null) {
lineas.add(linea);
}
return lineas;
}
// El recurso se cierra automáticamente gracias a try-with-resources
}
Este método:
- Declara que puede lanzar
IOException
conthrows IOException
- Utiliza try-with-resources para garantizar el cierre de recursos
- Permite que el código que lo llama decida cómo manejar los errores
El código que llama a este método puede entonces implementar su propia estrategia:
public void procesarArchivo(String ruta) {
try {
List<String> lineas = leerLineasArchivo(ruta);
for (String linea : lineas) {
// Procesar cada línea
}
} catch (FileNotFoundException e) {
// Crear el archivo si no existe
crearArchivoVacio(ruta);
} catch (IOException e) {
// Registrar el error y notificar al usuario
logger.error("Error al procesar el archivo: " + ruta, e);
mostrarErrorUsuario("No se pudo procesar el archivo. Verifique los permisos.");
}
}
Estrategias para excepciones específicas
FileNotFoundException
Esta excepción ocurre cuando intentamos abrir un archivo que no existe:
try {
File archivo = new File("configuracion.properties");
if (!archivo.exists()) {
// Crear archivo con configuración predeterminada
crearConfiguracionPredeterminada(archivo);
}
FileInputStream fis = new FileInputStream(archivo);
// Continuar con la lectura
} catch (FileNotFoundException e) {
// Este bloque se ejecutará solo si el archivo se eliminó
// entre la verificación de exists() y la creación del FileInputStream
System.err.println("El archivo fue eliminado inesperadamente");
}
EOFException
Se lanza cuando se alcanza el final de un archivo antes de lo esperado:
try (DataInputStream dis = new DataInputStream(
new FileInputStream("datos.bin"))) {
while (true) {
// Leer hasta que se lance EOFException
int valor = dis.readInt();
procesarValor(valor);
}
} catch (EOFException e) {
// Fin normal del archivo
System.out.println("Lectura completada");
} catch (IOException e) {
// Otros errores de I/O
System.err.println("Error durante la lectura: " + e.getMessage());
}
SocketException y excepciones de red
Para operaciones de red, es importante manejar diferentes tipos de errores:
try {
URL url = new URL("https://api.ejemplo.com/datos");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
conexion.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conexion.getInputStream()))) {
// Procesar respuesta
String linea;
while ((linea = reader.readLine()) != null) {
System.out.println(linea);
}
}
} catch (MalformedURLException e) {
System.err.println("URL inválida: " + e.getMessage());
} catch (UnknownHostException e) {
System.err.println("No se puede conectar al servidor. Verifique su conexión a internet.");
} catch (ConnectException e) {
System.err.println("Servidor no disponible. Intente más tarde.");
} catch (SocketTimeoutException e) {
System.err.println("La conexión ha excedido el tiempo de espera.");
} catch (IOException e) {
System.err.println("Error de I/O: " + e.getMessage());
}
Manejo de excepciones con NIO.2
La API NIO.2 introduce nuevas excepciones y patrones para el manejo de errores:
try {
Path ruta = Paths.get("documentos", "informe.txt");
List<String> lineas = Files.readAllLines(ruta, StandardCharsets.UTF_8);
// Procesar líneas
} catch (NoSuchFileException e) {
System.err.println("El archivo no existe: " + e.getFile());
// Crear archivo vacío
try {
Files.createFile(e.getFile());
} catch (IOException ex) {
System.err.println("No se pudo crear el archivo: " + ex.getMessage());
}
} catch (AccessDeniedException e) {
System.err.println("Permiso denegado para acceder a: " + e.getFile());
// Solicitar elevación de privilegios o cambiar ubicación
} catch (IOException e) {
System.err.println("Error de I/O: " + e.getMessage());
}
NIO.2 proporciona excepciones más específicas como:
- NoSuchFileException: El archivo no existe
- DirectoryNotEmptyException: Intento de eliminar un directorio no vacío
- AccessDeniedException: Permisos insuficientes
- FileAlreadyExistsException: El archivo ya existe
Creación de excepciones personalizadas
Para aplicaciones complejas, es útil crear excepciones personalizadas que encapsulen errores específicos de I/O:
public class ConfiguracionException extends IOException {
public ConfiguracionException(String mensaje) {
super(mensaje);
}
public ConfiguracionException(String mensaje, Throwable causa) {
super(mensaje, causa);
}
}
// Uso
public void cargarConfiguracion(Path archivo) throws ConfiguracionException {
try {
Properties propiedades = new Properties();
try (InputStream input = Files.newInputStream(archivo)) {
propiedades.load(input);
}
// Validar configuración
if (!propiedades.containsKey("db.url")) {
throw new ConfiguracionException("Falta la propiedad obligatoria db.url");
}
// Aplicar configuración
aplicarConfiguracion(propiedades);
} catch (IOException e) {
throw new ConfiguracionException("Error al cargar el archivo de configuración", e);
}
}
Logging en lugar de impresión de errores
En aplicaciones de producción, es mejor utilizar un sistema de logging en lugar de imprimir mensajes de error:
import java.util.logging.Level;
import java.util.logging.Logger;
public class GestorArchivos {
private static final Logger logger = Logger.getLogger(GestorArchivos.class.getName());
public byte[] leerArchivoBinario(String ruta) {
try {
Path path = Paths.get(ruta);
return Files.readAllBytes(path);
} catch (NoSuchFileException e) {
logger.log(Level.WARNING, "Archivo no encontrado: {0}", ruta);
return new byte[0];
} catch (IOException e) {
logger.log(Level.SEVERE, "Error al leer archivo: " + ruta, e);
throw new RuntimeException("No se pudo leer el archivo", e);
}
}
}
Estrategias de recuperación
Un manejo avanzado de excepciones incluye estrategias de recuperación:
public String leerArchivoConReintentos(String ruta, int maxIntentos) {
int intentos = 0;
long esperaBase = 1000; // milisegundos
while (intentos < maxIntentos) {
try {
return new String(Files.readAllBytes(Paths.get(ruta)), StandardCharsets.UTF_8);
} catch (IOException e) {
intentos++;
if (intentos >= maxIntentos) {
logger.severe("Error persistente al leer " + ruta + " después de " +
maxIntentos + " intentos: " + e.getMessage());
throw new RuntimeException("No se pudo leer el archivo después de múltiples intentos", e);
}
// Espera exponencial entre reintentos
long tiempoEspera = esperaBase * (long)Math.pow(2, intentos - 1);
logger.warning("Error al leer " + ruta + ", reintentando en " +
tiempoEspera + "ms. Intento " + intentos + "/" + maxIntentos);
try {
Thread.sleep(tiempoEspera);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Operación interrumpida", ie);
}
}
}
// Este punto nunca debería alcanzarse debido al throw en el catch
return null;
}
Mejores prácticas para el manejo de excepciones I/O
- Utiliza try-with-resources para garantizar el cierre de recursos
- Captura excepciones específicas antes que las generales
- Proporciona información útil en los mensajes de error
- Registra excepciones con un sistema de logging adecuado
- No ignores las excepciones sin una buena razón
- Considera la recuperación cuando sea posible
- Encapsula excepciones de bajo nivel en excepciones de dominio cuando sea apropiado
- Documenta las excepciones que pueden lanzar tus métodos
- Prueba los caminos de error en tu código, no solo el flujo normal
- Utiliza aserciones para validar precondiciones y postcondiciones
// Ejemplo que combina varias mejores prácticas
public List<Usuario> cargarUsuarios(Path archivo) throws DatosException {
Objects.requireNonNull(archivo, "La ruta del archivo no puede ser null");
List<Usuario> usuarios = new ArrayList<>();
try (BufferedReader reader = Files.newBufferedReader(archivo, StandardCharsets.UTF_8)) {
String linea;
int numeroLinea = 0;
while ((linea = reader.readLine()) != null) {
numeroLinea++;
// Omitir líneas vacías y comentarios
if (linea.trim().isEmpty() || linea.startsWith("#")) {
continue;
}
try {
Usuario usuario = parsearUsuario(linea);
usuarios.add(usuario);
} catch (FormatoInvalidoException e) {
logger.warning("Error en línea " + numeroLinea + ": " + e.getMessage());
// Continuar con la siguiente línea
}
}
return usuarios;
} catch (NoSuchFileException e) {
throw new DatosException("El archivo de usuarios no existe: " + archivo, e);
} catch (IOException e) {
throw new DatosException("Error al leer el archivo de usuarios", e);
}
}
El manejo efectivo de excepciones I/O es un equilibrio entre robustez, claridad y capacidad de recuperación. Al implementar estas estrategias, tus aplicaciones podrán manejar graciosamente los inevitables errores que ocurren durante las operaciones de entrada y salida.
Aprendizajes de esta lección
- Comprender los conceptos básicos de flujos de bytes y caracteres en Java.
- Aprender a manipular archivos y directorios usando las APIs tradicionales y NIO.2.
- Aplicar el patrón try-with-resources para un manejo seguro y eficiente de recursos.
- Identificar y manejar adecuadamente las excepciones comunes en operaciones de I/O.
- Implementar buenas prácticas y estrategias avanzadas para el manejo de errores en I/O.
Completa Java y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs