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.
Maneja bytes crudos (8-bit signed byte). Ideal para imágenes PNG, audio, PDFs y datos binarios.
Decodifica bytes a texto Unicode (16-bit char) aplicando un Charset (UTF-8, ISO-8859-1).
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.
El buffer acumula 8192 bytes en RAM en una sola llamada al kernel. Las siguientes 8191 lecturas se resuelven en memoria.
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().
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();
} 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);
} 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.
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.
Guardamos Factura(id, monto). Hash dinámico calculado: 0x84A1
Agregamos emailCliente al código fuente. Nuevo hash: 0x32B9
❌ InvalidClassException: local class incompatible (stream: 0x84A1, local: 0x32B9)
// Declarar siempre un identificador de versión explícito:
private static final long serialVersionUID = 1L; 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.
- 📁 META-INF/
- 📄 MANIFEST.MF
- 📁 com/empresa/app/
- 📄 Main.class
- 📄 Servicio.class
- 📄 Modelo.class
- 📁 resources/
- 🖼️ logo.png
Manifest-Version: 1.0
Main-Class: com.empresa.app.Main
Created-By: 21.0.2 (Eclipse Adoptium) $ 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 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.
Genera archivos de bytecode .class.
Genera el archivo ejecutable .jar con su manifiesto.
Corta un JRE mínimo (30 MB en vez de 300 MB del JDK).
Produce instaladores .exe, .dmg o .deb 100% autónomos.