Archivos e Interfaz de Usuario

Los Streams Entrada y Salida

En este capítulo se explican los Streams de Entrada y Salida en Java, que son fundamentales para manejar la lectura y escritura de datos en archivos y otros medios.

¿Qué son los Streams de Entrada y Salida?

Los Streams de Entrada y Salida en Java son una parte fundamental de la API de E/S (Entrada/Salida) que permite a los programadores leer y escribir datos de manera eficiente. Un Stream es una secuencia de datos que se puede leer o escribir, y se utiliza para manejar la entrada y salida de datos en diferentes formatos, como archivos, redes, memoria, etc.

Tipos de Streams

En Java, existen dos tipos principales de Streams:

  1. Streams de Bytes: Estos Streams manejan datos en forma de bytes. Son útiles para trabajar con archivos binarios, como imágenes o archivos ejecutables. Las clases principales para manejar Streams de Bytes son InputStream y OutputStream.
  2. Streams de Caracteres: Estos Streams manejan datos en forma de caracteres. Son ideales para trabajar con archivos de texto. Las clases principales para manejar Streams de Caracteres son Reader y Writer.

Estas categorías se dividen en varias subclases que proporcionan funcionalidades específicas para diferentes tipos de datos y fuentes de entrada/salida.

Subclases de Streams de Bytes

  • FileInputStream y FileOutputStream: Para leer y escribir archivos.
  • BufferedInputStream y BufferedOutputStream: Para mejorar el rendimiento al leer y escribir datos al usar un buffer.
  • DataInputStream y DataOutputStream: Para leer y escribir datos primitivos de Java de manera eficiente.
  • ObjectInputStream y ObjectOutputStream: Para leer y escribir objetos Java completos.

Subclases de Streams de Caracteres

  • FileReader y FileWriter: Para leer y escribir archivos de texto.
  • BufferedReader y BufferedWriter: Para mejorar el rendimiento al leer y escribir datos al usar un buffer.
  • PrintWriter: Para escribir datos de texto de manera conveniente, con métodos para imprimir diferentes tipos de datos.
  • InputStreamReader y OutputStreamWriter: Para convertir entre Streams de Bytes y Streams de Caracteres.
  • StringReader y StringWriter: Para leer y escribir datos de texto en memoria.

Uso de Streams

Algo que debe de quedar claro es que los Streams en Java son unidireccionales, lo que significa que un Stream de Entrada solo puede ser utilizado para leer datos, mientras que un Stream de Salida solo puede ser utilizado para escribir datos. Para manejar ambos tipos de operaciones, se deben utilizar Streams separados. Por ejemplo, para leer un archivo de texto, se puede usar un FileReader junto con un BufferedReader para mejorar el rendimiento:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LeerArchivo {
    void main() {
        String rutaArchivo = "ruta/al/archivo.txt";
        
        try (BufferedReader br = new BufferedReader(new FileReader(rutaArchivo))) {
            String linea;
            while ((linea = br.readLine()) != null) {
                System.out.println(linea);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Quizás te preguntes por qué el bloque try tiene paréntesis, esto se debe a que se está utilizando el bloque try-with-resources, que es una forma de asegurar que los recursos se cierren automáticamente después de su uso, evitando así posibles fugas de recursos. En este caso, el Stream BufferedReader se cerrará automáticamente al finalizar el bloque try, incluso si ocurre una excepción.

De manera similar, para escribir en un archivo de texto, se puede usar un FileWriter junto con un BufferedWriter:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class EscribirArchivo {
    void main() {
        String rutaArchivo = "ruta/al/archivo.txt";
        String contenido = "Este es un ejemplo de escritura en un archivo.";

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(rutaArchivo))) {
            bw.write(contenido);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

En este ejemplo, se escribe una cadena de texto en un archivo utilizando un BufferedWriter, y el bloque try-with-resources asegura que el Stream se cierre correctamente después de su uso.

Es importante manejar las excepciones de E/S adecuadamente, ya que pueden ocurrir errores como la falta de permisos para acceder a un archivo, el archivo no existe, o problemas de hardware. Siempre se recomienda utilizar bloques try-catch para capturar y manejar estas excepciones de manera efectiva.
El bloque try-with-resources es una característica introducida en Java 7 que facilita la gestión de recursos, como Streams, al garantizar que se cierren automáticamente después de su uso. Esto es especialmente útil para evitar fugas de recursos y asegurar que los Streams se liberen correctamente, incluso en caso de excepciones. Y algo relevante es que este bloque se puede utilizar con cualquier clase que implemente la interfaz AutoCloseable, lo que incluye a la mayoría de los Streams en Java. Así mismo podemos usarlo con múltiples recursos, separándolos por punto y coma dentro de los paréntesis del bloque try.

try-with-resources con múltiples Streams

En algunos casos, es posible que necesitemos manejar múltiples Streams al mismo tiempo, como al leer de un archivo y escribir en otro. El bloque try-with-resources nos permite gestionar ambos Streams de manera eficiente, asegurando que ambos se cierren correctamente después de su uso. Aquí hay un ejemplo de cómo hacerlo:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopiarArchivo {
    void main() {
        String rutaArchivoOrigen = "ruta/al/archivo_origen.txt";
        String rutaArchivoDestino = "ruta/al/archivo_destino.txt";

        try (BufferedReader br = new BufferedReader(new FileReader(rutaArchivoOrigen));
             BufferedWriter bw = new BufferedWriter(new FileWriter(rutaArchivoDestino))) {
            
            String linea;
            while ((linea = br.readLine()) != null) {
                bw.write(linea);
                bw.newLine(); // Agrega una nueva línea después de cada línea escrita
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

En este ejemplo, se utilizan dos Streams: un BufferedReader para leer el contenido de un archivo de origen y un BufferedWriter para escribir ese contenido en un archivo de destino. Ambos Streams se gestionan dentro del mismo bloque try-with-resources, lo que garantiza que se cierren automáticamente al finalizar el bloque, incluso si ocurre una excepción durante la lectura o escritura.

Conclusión

Los Streams de Entrada y Salida en Java son herramientas esenciales para manejar la lectura y escritura de datos en archivos y otros medios. Comprender cómo funcionan los diferentes tipos de Streams y cómo gestionarlos adecuadamente, especialmente utilizando el bloque try-with-resources, es fundamental para escribir código eficiente y seguro que maneje correctamente los recursos de E/S. Al utilizar los Streams de manera adecuada, podemos evitar problemas comunes como fugas de recursos y asegurarnos de que nuestros programas manejen los datos de manera eficiente y segura.

Copyright Jesús Aurelio Castro Magaña © 2026