En el ecosistema de Java, la manipulación eficiente de datos persistentes —como archivos locales o recursos de red— se basa fundamentalmente en el paquete java.io. Este sistema se organiza alrededor del concepto de flujo (stream), una abstracción que representa un canal secuencial de transferencia de información entre una fuente y un destino, tomando siempre como referencia la memoria principal.
Clasificación fundamental
Los flujos se categorizan según dos criterios esenciales:
- Dirección de flujo:
- Entrada (input): transportan datos desde un dispositivo externo (disco, red, teclado) hacia la memoria RAM.
- Salida (output): transfieren datos desde la memoria RAM hacia un destino externo (archivo, consola, socket).
- Unidad de procesamiento:
- Flujos de bytes: operan a nivel de octeto (
byte). Son universales: pueden manejar cualquier tipo de dato binario (texto, imágenes, audio, ejecutables). - Flujos de caracteres: trabajan con unidades lógicas de texto (
charoString). Están optimizados para texto codificado (UTF-8, UTF-16, etc.) y resuelven automáticamente problemas de codificación y decodificación.
- Flujos de bytes: operan a nivel de octeto (
Jerarquía de clases base
Todas las implementaciones siguen una estructura jerárquica clara:
| Entrada | Salida | |
|---|---|---|
| Bytes | InputStream |
OutputStream |
| Caracteres | Reader |
Writer |
Flujos de bytes: manejo de datos crudos
Como todo dato digital se almacena en forma binaria, los flujos de bytes son la capa más baja y versátil. Su uso implica gestionar explícitamente la codificación cuando se trabaja con texto.
Ejemplo práctico: copia segura de un archivo binario
public class BinaryFileCopy {
public static void copyFile(String sourcePath, String targetPath) throws IOException {
try (FileInputStream input = new FileInputStream(sourcePath);
FileOutputStream output = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[8192]; // Buffer de 8 KB
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
}
public static void main(String[] args) {
try {
copyFile("imagen_original.png", "imagen_copia.png");
System.out.println("Copia binaria completada.");
} catch (IOException e) {
System.err.println("Error durante la copia: " + e.getMessage());
}
}
}
Flujos de caracteres: manejo inteligente de texto
Cuando el objetivo es procesar contenido textual, los flujos de caracteres ofrecen una interfaz más intuitiva y segura, ya que encapsulan la lógica de conversión entre bytes y caracteres según la codificación especificada.
Ejemplo práctico: lectura y escritura de texto con codificación UTF-8
public class TextFileProcessor {
private static final String ENCODING = "UTF-8";
public static void processTextFiles(String inputPath, String outputPath) throws IOException {
try (FileReader reader = new FileReader(inputPath, ENCODING);
FileWriter writer = new FileWriter(outputPath, ENCODING)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
// Transformación simple: convertir a mayúsculas
String processed = new String(buffer, 0, charsRead).toUpperCase();
writer.write(processed);
}
}
}
public static void main(String[] args) {
try {
processTextFiles("entrada.txt", "salida_mayusculas.txt");
System.out.println("Procesamiento de texto completado.");
} catch (IOException e) {
System.err.println("Error de E/S: " + e.getMessage());
}
}
}
Gestión robusta de recursos
Desde Java 7, la construcción try-with-resources garantiza la liberación automática de recursos que implementen AutoCloseable, eliminando errores comunes por olvido de cierre:
public class SafeResourceHandling {
public static void safeWrite(String filename, String content) {
// El flujo se cierra automáticamente al salir del bloque try
try (BufferedWriter bw = Files.newBufferedWriter(
Paths.get(filename), StandardCharsets.UTF_8)) {
bw.write(content);
bw.newLine();
bw.write("Generado el: " + LocalDateTime.now());
} catch (IOException e) {
throw new UncheckedIOException("No se pudo escribir en " + filename, e);
}
}
}
Elección estratégica
- Usar flujos de bytes cuando se manipulan recursos binarios (imágenes, PDF, archivos comprimidos) o cuando se requiere control absoluto sobre los datos sin interpretación.
- Usar flujos de caracteres exclusivamente para contenido textual, especialmente cuando se necesita soporte multiplataforma y manejo correcto de caracteres Unicode (acentos, emojis, idiomas asiáticos).