UP | HOME

Lectura/Escritura de ficheros binarios

Table of Contents

Leer ficheros binarios

En los ficheros binarios como JPEG o PDF, por ejemplo, los bytes no representan letras, números… no se hace traducción de los bytes a gráficos que representan esos símbolos usando tablas como ASCII o UTF-8. Esto hace que no sea posible, ni tenga sentido, leer estos ficheros línea a línea, porque no existe el símbolo \n en dichos ficheros.

Así pues, lo que podríamos pensar es que la mejor opción para leer ficheros binarios es hacerlo byte a byte, de uno en uno, y buscando de esta manera el significado de estos bytes. Tampoco es lo mejor porque cada lectura de un bytes es un acceso a disco lo que es muy lento y lastraría el rendimiento del programa. Ten en cuenta que pueden haber millones, si no miles de millones, de bytes en un fichero binario.

¿Cómo se deben leer entonces los ficheros binarios? Te muestro 4 opciones que puedes usar en función de los objetivos que busques y/o las necesidades a las que te enfrentes:

a) Leer y cargar todo el fichero binario en un byte[]

  • Es una opción válida si el fichero binario es pequeño.
  • No es necesario cerrar de forma explícita el fichero, lo hace Files automáticamente.
var ruta = Path.of("/usr/share/datos.bin");
byte[] contenido = Files.readAllBytes(ruta);

b) Leer por bloques de bytes

  • Si el fichero es grande podemos cargarlo y procesarlo por bloques de bytes.
  • No se usa búfer intermedio de Java.
  • Cada vez que se llama a read se hace una llamada el sistema.
  • Es necesario cerrar el fichero o hacerlo en un try-with-resource.

Por ejemplo, este programa procesa el fichero en bloques de 8KiB:

var ruta = Path.of("/usr/share/datos.bin");
byte[] buffer = new byte[8192];
int bytesLeidos;

try (InputStream in = Files.newInputStream(ruta)) {
    while ((bytesLeidos = in.read(buffer)) != -1) {
        // procesar el bloque de bytes leídos y cargados en buffer
    }
} catch (IOException e) {
    // manejar la excepción
}

c) Leer por bloques de bytes usando búfer intermedio

  • Si el fichero es grande podemos cargarlo y procesarlo por bloques de bytes.
  • Se usa búfer intermedio en Java lo que permite una mejora en el rendimiento porque se hacen menos llamadas al sistema.
  • Es necesario cerrar el fichero o hacerlo en un try-with-resource.

Por ejemplo, este programa procesa el fichero en bloques de 8KiB:

var ruta = Path.of("/usr/share/datos.bin");
byte[] buffer = new byte[8192];
int bytesLeidos;

try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(ruta))) {
    while ((bytesLeidos = bis.read(buffer)) != -1) {
        // procesar el bloque de bytes leídos y cargados en buffer
    }
} catch (IOException e) {
    // manejar la excepción
}

d) Leer en bloques de tamaño fijo (datos primitivos - estructuras)

Muchos archivos binarios como JPEG, PDF, FIT… están organizados en cabeceras y campos de tamaño fijo. Así, estos se pueden leer solicitando los bytes exactos que ocupa cada tipo de dato.

Imagina que queremos leer un fichero binario cuya estructura es la siguiente:

  1. 1 byte para la cabecera.
  2. 4 bytes que codifican un día y hora determinado (un timestamp).
  3. 2 bytes que codifican la temperatura que hizo ese día a esa hora.
Path ruta = Path.of("/usr/share/datos.bin");
try (DataInputStream dis = new DataInputStream(new BufferedInputStream(Files.newInputStream(ruta)))) {
    byte cabecera = dis.readByte();      // Lee 1 byte
    int fechaConHora = dis.readInt();    // Lee 4 bytes
    short temperatura = dis.readShort(); // Lee 2 bytes      
} catch (IOException e) {
    // manejar la excepción
}

Escribir ficheros binarios

De manera simétrica a la lectura, al escribir ficheros binarios no enviamos texto ni caracteres traducidos, sino bloques de bytes puros.

Siguiendo la misma lógica que en la lectura, disponemos de los mismos enfoques:

a) Escribir todos los bytes al fichero de golpe

  • Cuando tienes todos los datos a escribir: mejor hacerlo de golpe.
  • No se usa buffer intermedio.
  • La clase de utilidad Files se encarga de abrir el fichero, guardar, hacer flush y cerrar.
var path = Path.of("oswrite.bin");
try {
    byte[] buffer = { 0x48, 0x4f, 0x4c, 0x41 };
    Files.write(path, buffer);
} catch (IOException e) {
    System.err.println(e);
}

b) Escribir el fichero por bloques de bytes

  • Se escriben poco a poco, por bloques de bytes.
  • No se usa búfer intermedio.
  • Se hace una llamada al sistema cada vez que se llama a write.
  • Se tiene que abrir el fichero, hacer el flush y cerrar de forma explícita o en un try-with-resource.
var path = Path.of("oswrite.bin");
try (OutputStream os = Files.newOutputStream(path)) {
    byte[] buffer = { 0x48, 0x4f, 0x4c, 0x41 };
    os.write(buffer);
} catch (IOException e) {
    System.err.println(e);
}

c) Escribir el fichero por bloques de bytes con búfer intermedio

  • Como el caso anterior por usando búfer intermedio.
  • Este búfer intermedio reduce el número de llamadas al sistema.
var path = Path.of("boswrite.bin");
try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(path))) {
    byte[] buffer = { 0x48, 0x4f, 0x4c, 0x41 };
    bos.write(buffer);
} catch (IOException e) {
    System.err.println(e);
}

d) Escribir en bloques de tamaño fijo (datos primitivos - estructuras)

  • Se escriben datos primitivos o estructuras binarias: int, short, double…
  • Se puede usar o no búfer, dependiendo del decorator a usar.
  • Se tiene que abrir el fichero, hacer el flush y cerrar de forma explícita o hacerlo en un try-with-resource.

Esta opción se utiliza cuando queremos generar o guardar un fichero binario estructurado (un archivo de configuración propio, exportación de métricas, cabeceras, etc) donde cada dato primitivo de Java debe ocupar su número exacto de bytes en el disco.

Te muestro dos ejemplos. El equivalente a los anteriores:

var path = Path.of("boswrite.bin");
try (DataOutputStream dos = new DataOutputStream(
    new BufferedOutputStream(Files.newOutputStream(path)))) {
    dos.writeByte(0x48);     // Escribe 1 byte
    dos.writeShort(0x4f4c);  // Escribe 2 bytes
    dos.writeByte(0x41);     // Escribe 1 byte
} catch (IOException e) {
    System.err.println(e);
}

Un nuevo ejemplo donde se pretende escribir, en un fichero binario, una cabecera de 1 byte, una fecha y hora en formato timestamp y una temperatura (1 byte de cabecera, 4 bytes de timestamp y 2 bytes de temperatura):

Path ruta = Path.of("salida.bin");

try (DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(ruta)))) {
    byte cabecera = 0x01;
    int fechaConHora = 1700000000; // Epoch timestamp (4 bytes)
    short temperatura = 24;        // Grados celsius (2 bytes)

    dos.writeByte(cabecera);        // Escribe 1 byte
    dos.writeInt(fechaConHora);     // Escribe 4 bytes
    dos.writeShort(temperatura);    // Escribe 2 bytes
} catch (IOException e) {
    // manejar la excepción
}

Resumen lectura/escritura binaria en Java

En la API de Java I/O existe una simetría casi perfecta entre las operaciones de entrada (InputStream / Files) y de salida (OutputStream / Files).

Escenario Lectura (Input) Escritura (Output) Uso principal / Características
a) De un solo golpe Files.readAllBytes(path) Files.write(path, bytes) Lectura o escritura delfichero entero en un byte[]
b) Por bloques (Sin búfer Java) Files.newInputStream(path) Files.newOutputStream(path) Lectura o escritura por bloques de byte[] sin búfer de Java
c) Por bloques (Con búfer Java) BufferedInputStream BufferedOutputStream Lectura o escritura por bloques de byte[] usando búfer de Java
d) Estructuras / Primitivos DataInputStream DataOutputStream Lectura o escritura de fichero binarios con estructura

Regla práctica de rendimiento:

  • Para opciones a) y b) no necesitas añadir un decorador de búfer. En a) la clase Files gestiona todo el ciclo de vida, y en b) el propio array byte[] que le pasas al método read() o write() hace la función de búfer.
  • Para la opción d), es una buena práctica interponer un BufferedInputStream / BufferedOutputStream entre el fichero y la clase DataInput/OutputStream para no realizar llamadas al sistema con cada tipo primitivo leído o escrito.

Author: Román Ginés Martínez Ferrández

Created: 2026-09-06 dom 16:23

Validate