Anexando a um ObjectOutputStream
-
19-09-2019 - |
Pergunta
Não é possível anexar a um ObjectOutputStream
?
Eu estou tentando anexar a uma lista de objetos. Seguinte trecho é uma função que é chamado sempre que um trabalho está terminado.
FileOutputStream fos = new FileOutputStream
(preferences.getAppDataLocation() + "history" , true);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject( new Stuff(stuff) );
out.close();
Mas quando eu tento lê-lo eu só pegar o primeiro no arquivo.
Então eu recebo java.io.StreamCorruptedException
.
Para ler Estou 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() );
}
Eu não sei quantos objetos estarão presentes, por isso estou lendo enquanto não há exceções. Desde que o Google diz que isso não é possível. Fiquei me perguntando se alguém sabe uma maneira?
Solução
Aqui está o truque: ObjectOutputStream
subclasse e substituir o 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 usá-lo, basta verificar se o arquivo de histórico existe ou não e instanciar quer este fluxo appendable (no caso o arquivo existe = Anexamos = nós não queremos um cabeçalho) ou o fluxo original (no caso o arquivo faz não existe = precisamos de um cabeçalho).
Editar
Eu não estava feliz com a primeira nomeação da classe. Essa é melhor: ele descreve o 'o que é para' em vez do 'como se faz'
Editar
Mudou o nome mais uma vez, para esclarecer, que este fluxo é apenas para acrescentar a um arquivo existente. Ele não pode ser usado para criar um new arquivo com dados de objeto.
Editar
Adicionado uma chamada para reset()
após esta questão mostrou que a versão original que apenas cancelou writeStreamHeader
ser um não-op podia sob algumas condições criar um fluxo que não pôde ser lido.
Outras dicas
Como o API diz, o construtor ObjectOutputStream
escreve o cabeçalho fluxo de serialização para o fluxo subjacente. E este cabeçalho é esperado para ser apenas uma vez, no início do arquivo. Então, chamando
new ObjectOutputStream(fos);
várias vezes na FileOutputStream
que se refere ao mesmo ficheiro vai escrever o cabeçalho várias vezes e corromper o arquivo.
Devido ao formato preciso do arquivo serializado, anexando vai realmente corrompê-lo. Você tem que escrever todos os objetos para o arquivo como parte da mesma corrente, ou então ele irá falhar quando ele lê os metadados fluxo quando se está esperando um objeto.
Você pode ler o serialização Especificação para mais detalhes, ou (mais fácil) ler esta discussão , onde Roedy Green diz basicamente o que eu disse.
A maneira mais fácil de evitar este problema é manter o aberto OutputStream quando você gravar os dados, em vez de fechá-lo depois de cada objeto. Chamando reset()
pode ser aconselhável para evitar uma fuga de memória.
A alternativa seria ler o arquivo como uma série de ObjectInputStreams consecutivos também. Mas isso requer que você mantenha contar quantos bytes você ler (isso pode ser implementd com um FilterInputStream), em seguida, feche o InputStream, abri-lo novamente, ignorar que muitos bytes e só então envolvê-la em um ObjectInputStream ().
Que tal antes de cada vez que você adicionar um objeto, ler e copiar todos os dados atuais no arquivo e, em seguida, substituir todos juntos para arquivo.
Eu tenho estendido a solução aceita para criar uma classe que pode ser utilizado tanto para anexando e criando novo arquivo.
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 classe pode ser usado como um substituto estendida direta para ObjectOutputStream. Nós podemos usar a classe da seguinte forma:
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();
}
}
}