Agregar a un ObjectOutputStream
¿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.
Aquí está el truco: subclase ObjectOutputStream
y anule el 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 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ó writeStreamHeader
para ser no operativa podría, bajo algunas condiciones, crear una transmisión que no se podía leer.
Como dice la API , el ObjectOutputStream
constructor 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 FileOutputStream
que hace referencia al mismo archivo se escribirá el encabezado varias veces y se dañará el archivo.
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.
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().
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();
}
}
}