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?

Foi útil?

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();
        }

    }

}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top