Pregunta

No es posible anexar a una ObjectOutputStream?

Estoy tratando de añadir a una lista de objetos. Siguiente fragmento es una función que se llama cuando se termina un trabajo.

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

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

Pero cuando trato de leer que solo me dan la primera en el archivo. Luego llego java.io.StreamCorruptedException.

Para leer Estoy utilizando

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 por lo que estoy leyendo, mientras que no hay excepciones. De lo que Google dice que esto no es posible. Me preguntaba si alguien sabe de una manera?

¿Fue útil?

Solución

Aquí está el truco: ObjectOutputStream subclase y reemplazar el método writeStreamHeader:

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 comprobar si existe el archivo histórico o no, y ya sea una instancia de esta corriente puede agregar nada (en caso de que el archivo existe = = añadimos que no queremos una cabecera) o el flujo original (en caso de que el archivo no no existimos = necesitamos una cabecera).

Editar

Yo no estaba contento con la primera denominación de la clase. es mejor éste: que describe el 'para qué sirve' en lugar de la 'cómo se hace'

Editar

Se ha cambiado el nombre una vez más, para aclarar, que esta corriente es sólo para anexar a un archivo existente. No se puede utilizar para crear un nueva archivo con datos de objeto.

Editar

Se ha añadido una llamada a reset() después esta pregunta mostró que la la versión original que simplemente hizo caso omiso de writeStreamHeader a ser un no-op pudo bajo algunas condiciones crean una corriente que no se podía leer.

Otros consejos

A medida que el API dice, el constructor ObjectOutputStream escribe la cabecera de flujo de serialización a la corriente subyacente. Y se espera que esta cabecera para ser sólo una vez, en el principio del archivo. Así llamando

new ObjectOutputStream(fos);

varias veces en el FileOutputStream que hace referencia al mismo archivo de cabecera escribirán las varias veces y dañar el archivo.

Debido al formato preciso del archivo serializado, añadiendo hecho va a corromperlo. Usted tiene que escribir todos los objetos en el archivo como parte de la misma corriente, o de lo contrario se bloqueará cuando lee los metadatos corriente cuando se está esperando un objeto.

Se podía leer el Especificación serialización para más detalles, o (más fácil) leer este hilo donde Roedy Green dice básicamente lo que acabo de decir.

La forma más sencilla de evitar este problema es mantener el OutputStream abierta al escribir los datos, en lugar de cerrarlo después de cada objeto. Llamando reset() podría ser aconsejable para evitar una pérdida de memoria.

La alternativa sería leer el archivo como una serie de ObjectInputStreams consecutivos también. Pero esto requiere que llevar la cuenta la cantidad de bytes que lee (esto se puede implementd con un FilterInputStream), a continuación, cierre el InputStream, abrirlo de nuevo, omitir que muchos bytes y sólo entonces lo envuelve en un ObjectInputStream ().

¿Qué tal antes de cada vez que se anexa un objeto, leer y copiar todos los datos actuales en el archivo y sobrescribir todos juntos en un archivo.

He ampliado la solución aceptada para crear una clase que puede ser utilizado tanto para anexar y crear nuevo archivo.

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 un reemplazo directo prolongado para ObjectOutputStream. Podemos utilizar 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();
        }

    }

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top