Agregar a un ObjectOutputStream

Resuelto Hamza Yerlikaya asked hace 15 años • 6 respuestas

¿No es posible agregar a un ObjectOutputStream?

Estoy intentando agregarlo a una lista de objetos. El siguiente fragmento es una función que se llama cada vez que finaliza un trabajo.

FileOutputStream fos = new FileOutputStream
           (preferences.getAppDataLocation() + "history" , true);
ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject( new Stuff(stuff) );
out.close();

Pero cuando intento leerlo solo aparece el primero del archivo. Entonces lo entiendo java.io.StreamCorruptedException.

Para leer estoy usando

FileInputStream fis = new FileInputStream
        ( preferences.getAppDataLocation() + "history");
ObjectInputStream in = new ObjectInputStream(fis);    

try{
    while(true)
        history.add((Stuff) in.readObject());
}catch( Exception e ) { 
    System.out.println( e.toString() );
}

No sé cuántos objetos estarán presentes, así que estoy leyendo mientras no haya excepciones. Por lo que dice Google esto no es posible. Me preguntaba si alguien sabe alguna manera.

Hamza Yerlikaya avatar Jul 28 '09 21:07 Hamza Yerlikaya
Aceptado

Aquí está el truco: subclase ObjectOutputStreamy anule el writeStreamHeadermétodo:

public class AppendingObjectOutputStream extends ObjectOutputStream {

  public AppendingObjectOutputStream(OutputStream out) throws IOException {
    super(out);
  }

  @Override
  protected void writeStreamHeader() throws IOException {
    // do not write a header, but reset:
    // this line added after another question
    // showed a problem with the original
    reset();
  }

}

Para usarlo, simplemente verifique si el archivo histórico existe o no y cree una instancia de esta secuencia anexable (en caso de que el archivo exista = agregamos = no queremos un encabezado) o de la secuencia original (en caso de que el archivo no exista = necesitamos un encabezado).

Editar

No estaba contento con el primer nombre de la clase. Éste es mejor: describe "para qué sirve" en lugar de "cómo se hace".

Editar

Se cambió el nombre una vez más, para aclarar que esta secuencia solo se puede agregar a un archivo existente. No se puede utilizar para crear un nuevo archivo con datos de objetos.

Editar

Se agregó una llamada reset()después de que esta pregunta mostró que la versión original que simplemente se anuló writeStreamHeaderpara ser no operativa podría, bajo algunas condiciones, crear una transmisión que no se podía leer.

Andreas Dolk avatar Jul 28 '2009 15:07 Andreas Dolk

Como dice la API , el ObjectOutputStreamconstructor escribe el encabezado del flujo de serialización en el flujo subyacente. Y se espera que este encabezado aparezca solo una vez, al principio del archivo. Entonces llamando

new ObjectOutputStream(fos);

varias veces en el FileOutputStreamque hace referencia al mismo archivo se escribirá el encabezado varias veces y se dañará el archivo.

Tadeusz Kopec for Ukraine avatar Jul 28 '2009 15:07 Tadeusz Kopec for Ukraine

Debido al formato preciso del archivo serializado, agregarlo lo dañará. Debe escribir todos los objetos en el archivo como parte de la misma secuencia; de lo contrario, se bloqueará cuando lea los metadatos de la secuencia cuando espera un objeto.

Puede leer la Especificación de serialización para obtener más detalles o (más fácil) leer este hilo donde Roedy Green dice básicamente lo que acabo de decir.

Michael Myers avatar Jul 28 '2009 15:07 Michael Myers

La forma más sencilla de evitar este problema es mantener abierto OutputStream cuando escribe los datos, en lugar de cerrarlo después de cada objeto. reset()Puede ser recomendable llamar para evitar una pérdida de memoria.

La alternativa sería leer también el archivo como una serie de ObjectInputStreams consecutivos. Pero esto requiere que cuente cuántos bytes lee (esto se puede implementar con FilterInputStream), luego cierre el InputStream, ábralo nuevamente, omita esa cantidad de bytes y solo luego envuélvalo en un ObjectInputStream().

Michael Borgwardt avatar Jul 28 '2009 15:07 Michael Borgwardt

Extendí la solución aceptada para crear una clase que pueda usarse tanto para agregar como para crear archivos nuevos.

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class AppendableObjectOutputStream extends ObjectOutputStream {

    private boolean append;
    private boolean initialized;
    private DataOutputStream dout;

    protected AppendableObjectOutputStream(boolean append) throws IOException, SecurityException {
        super();
        this.append = append;
        this.initialized = true;
    }

    public AppendableObjectOutputStream(OutputStream out, boolean append) throws IOException {
        super(out);
        this.append = append;
        this.initialized = true;
        this.dout = new DataOutputStream(out);
        this.writeStreamHeader();
    }

    @Override
    protected void writeStreamHeader() throws IOException {
        if (!this.initialized || this.append) return;
        if (dout != null) {
            dout.writeShort(STREAM_MAGIC);
            dout.writeShort(STREAM_VERSION);
        }
    }

}

Esta clase se puede utilizar como reemplazo extendido directo de ObjectOutputStream. Podemos usar la clase de la siguiente manera:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ObjectWriter {

    public static void main(String[] args) {

        File file = new File("file.dat");
        boolean append = file.exists(); // if file exists then append, otherwise create new

        try (
            FileOutputStream fout = new FileOutputStream(file, append);
            AppendableObjectOutputStream oout = new AppendableObjectOutputStream(fout, append);
        ) {
            oout.writeObject(...); // replace "..." with serializable object to be written
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
Pratanu Mandal avatar Aug 07 '2019 15:08 Pratanu Mandal