Fundamentos de Entrada/Salida

El Universo de I/O: Bytes vs Caracteres

En disco todo son ceros y unos. La diferencia está en si la JVM los consume crudos (8 bits) o los decodifica a caracteres Unicode (16 bits) según un Charset.

Flujo Binario InputStream / OutputStream

Maneja bytes crudos (8-bit signed byte). Ideal para imágenes PNG, audio, PDFs y datos binarios.

01001011 11100010 00001111
Flujo de Texto Reader / Writer

Decodifica bytes a texto Unicode (16-bit char) aplicando un Charset (UTF-8, ISO-8859-1).

'C' 'a' 'n' 'c' 'i'
Simulador: La Trampa del Encoding Probá leer el texto "Canción" con diferentes encodings
Canción ✓ Decodificación Correcta
Arquitectura de Diseño

El Patrón Decorador: La Cebolla de Streams

En lugar de cientos de subclases, java.io compone capas concéntricas sobre la conexión física: Buffer en RAM y formateo de tipos.

DataInputStream (Tipos: readDouble, readUTF) BufferedInputStream (Caché 8 KB en RAM) FileInputStream "datos.bin" (Disco SSD / HDD)
Impacto en 10.000 Lecturas de Bytes:
Llamadas a Disco: 2 llamadas 1.8 ms (¡Ultra Rápido!)

El buffer acumula 8192 bytes en RAM en una sola llamada al kernel. Las siguientes 8191 lecturas se resuelven en memoria.

Evolución de la Plataforma

Java NIO.2 Moderno: java.nio.file

Java 7 reemplazó al frágil java.io.File por la inmutabilidad de Path, operaciones atómicas en Files y Lazy Streams con Files.walk().

Antiguo java.io.File (Java 1.0) 12 líneas, gestión manual de streams
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("file.txt"));
    String linea;
    while ((linea = br.readLine()) != null) {
        System.out.println(linea);
    }
} finally {
    if (br != null) br.close();
}
Moderno java.nio.file (NIO.2) 1 línea, atómico, charset explícito
Path path = Paths.get("file.txt");

// Lectura directa en 1 línea
List<String> lineas = Files.readAllLines(path, StandardCharsets.UTF_8);

// O Lazy Stream para archivos gigantes (GBytes):
try (Stream<Path> stream = Files.walk(path)) {
    stream.filter(Files::isRegularFile).forEach(System.out::println);
}
Persistencia Binaria de Objetos

Serialización: Aplanar el Heap a Disco

ObjectOutputStream convierte grafos de objetos vivos en una secuencia binaria que inicia con la firma 0xAC ED 00 05.

Objeto en Memoria RAM (Heap)
class Usuario implements Serializable
id = 101
nombre = "Facundo"
transient password = "secret_hash"
ObjectOutputStream
Archivo en Disco (usuario.ser)
0xAC ED 00 05 (Magic Header) 0x73 0x72 0x00 0x07 Usuario id: 101 | nombre: "Facundo" password: NULL (¡No persistido!)
Clave de Seguridad: La palabra reservada transient le ordena a la JVM omitir campos sensibles (contraseñas, sockets, conexiones a BD) al escribir a disco.
Contrato de Compatibilidad

El Rol Crítico de serialVersionUID

Si la clase cambia y no definiste serialVersionUID fijo, la JVM calcula un hash dinámico y la deserialización colapsa con InvalidClassException.

Paso 1
Versión 1 en Disco

Guardamos Factura(id, monto). Hash dinámico calculado: 0x84A1

Paso 2
Evolución de Código

Agregamos emailCliente al código fuente. Nuevo hash: 0x32B9

Paso 3
Lectura / Deserialización

❌ InvalidClassException: local class incompatible (stream: 0x84A1, local: 0x32B9)

Solución Obligatoria de Arquitectura
// Declarar siempre un identificador de versión explícito:
private static final long serialVersionUID = 1L;
Empaquetado de Artefactos

Anatomía de un Archivo JAR (Java Archive)

Un archivo .jar no es magia: es un archivo comprimido en formato ZIP estándar con bytecode compilado y un archivo descriptor MANIFEST.MF.

mi-aplicacion.jar (Estructura ZIP)
  • 📁 META-INF/
    • 📄 MANIFEST.MF
  • 📁 com/empresa/app/
    • 📄 Main.class
    • 📄 Servicio.class
    • 📄 Modelo.class
  • 📁 resources/
    • 🖼️ logo.png
META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: com.empresa.app.Main
Created-By: 21.0.2 (Eclipse Adoptium)
Comando de Ejecución:
$ java -jar mi-aplicacion.jar

La JVM abre el ZIP, lee la clave Main-Class del MANIFEST.MF y ejecuta inmediatamente el método public static void main(String[] args).

Distribución Moderna

Distribución Nativa con jpackage

La era de pedirle al usuario "Instalá Java antes de abrir mi app" terminó. jpackage crea instaladores nativos (.exe, .dmg, .deb) con un JRE embebido.

1
Compilación javac *.java

Genera archivos de bytecode .class.

→
2
Empaquetado jar -cfe app.jar

Genera el archivo ejecutable .jar con su manifiesto.

→
3
jlink (Tree-Shaking) jlink --strip-debug

Corta un JRE mínimo (30 MB en vez de 300 MB del JDK).

→
4
jpackage jpackage --type exe

Produce instaladores .exe, .dmg o .deb 100% autónomos.