48

¿No es posible agregar a un ObjectOutputStream?Anexando a un ObjectOutputStream

Estoy tratando de anexar a una lista de objetos. El siguiente fragmento es una función que se invoca cada vez que se 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 obtengo el primero en el archivo. Luego recibo 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 por lo que estoy leyendo, mientras que no hay excepciones. Por lo que Google dice, esto no es posible. Me preguntaba si alguien sabe una forma de hacerlo.

+0

¿Obtiene algo de la transmisión o produce una excepción la primera vez en el ciclo? – skaffman

+0

lee el primer objeto que guardé, luego recibo una excepción. –

+0

En el código anterior, solo veo un objeto escrito en el archivo, por lo que solo se leerá uno, ¿no? – aperkins

Respuesta

67

Aquí está el truco: subclase ObjectOutputStream y redefinir la writeStreamHeader mé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 comprobar si existe el archivo histórico o no, y crear una instancia ya sea esta corriente appendable (en caso de que el archivo existe = nos append = no queremos un encabezado) o la secuencia original (en caso de que el archivo no exista = necesitamos un encabezado).

Editar

que 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

cambió el nombre una vez más, para aclarar, que esta corriente es sólo para anexar a una ya existente archivo.No se puede usar para crear un nuevo archivo con datos de objeto.

Editar

añadido una llamada a reset() después this question demostró que 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.

+0

¡listo! Creo * que el encabezado del flujo es el único problema, en cuyo caso esto debería funcionar de maravilla, pero ¿lo has probado para estar seguro? –

+0

Gracias por sus comentarios :-) Sí, he publicado el código probado (simplemente se olvidó de mencionarlo en la respuesta) –

+0

+1, gracias. * Advertencia: el constructor ObjectOutputStream * llama al * writeStreamHeader(). Esto hace que al menos sea difícil para una sola clase manejar ambos casos. ¿Puede hacer un método de fábrica simple que devuelva fileExists? nuevo AppendingObjectOutputStream (out): nuevo ObjectOutputStream (out); –

7

Debido al formato preciso del archivo serializado, anexar lo corromperá. Debe escribir todos los objetos en el archivo como parte de la misma secuencia o, de lo contrario, se bloqueará cuando lea los metadatos de la secuencia cuando esté esperando un objeto.

Puede leer el Serialization Specification para obtener más detalles, o (más fácil) leer this thread donde Roedy Green dice básicamente lo que acabo de decir.

13

Como dice API, el constructor ObjectOutputStream escribe el encabezado de la secuencia de serialización en la secuencia subyacente. Y se espera que este encabezado sea solo una vez, al comienzo del archivo. Así llamando

new ObjectOutputStream(fos); 

varias veces en el FileOutputStream que hace referencia al mismo archivo va a escribir el encabezado varias veces y dañar el archivo.

5

La forma más fácil de evitar este problema es mantener el OutputStream abierto cuando escribe los datos, en lugar de cerrarlos después de cada objeto. Llamar a reset() puede 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 cuentes cuántos bytes has leído (esto se puede implementar con un FilterInputStream), luego cierras el InputStream, lo vuelves a abrir, saltas muchos bytes y luego lo envuelves en un ObjectInputStream().

0

¿Qué tal antes de agregar un objeto, leer y copiar todos los datos actuales en el archivo y luego sobrescribir todos juntos en el archivo?

Cuestiones relacionadas