Domanda

Non è possibile aggiungere a una ObjectOutputStream?

Sto cercando di aggiungere a un elenco di oggetti. Seguente frammento è una funzione che viene chiamata quando un lavoro è terminato.

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

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

Ma quando cerco di leggerlo ho solo il primo nel file. Poi ho java.io.StreamCorruptedException.

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

Non so quanti oggetti saranno presenti in modo che sto leggendo, mentre non ci sono eccezioni. Da quello che Google dice che questo non è possibile. Mi chiedevo se qualcuno conosce un modo?

È stato utile?

Soluzione

Ecco il trucco: sottoclasse ObjectOutputStream e l'override del metodo 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();
  }

}

Per usarlo, basta controllare se esiste il file di storia o no e istanziare o questo flusso per l'aggiunta (nel caso in cui si trova il file = aggiungiamo = non vogliamo un colpo di testa) o il flusso originale (nel caso in cui il file non non esistiamo = abbiamo bisogno di un colpo di testa).

Modifica

Non ero felice con la prima denominazione della classe. di meglio questo: descrive il 'che cosa è per' piuttosto che il 'come si fa'

Modifica

Cambiato il nome ancora una volta, per chiarire che questo flusso è solo per aggiungendo ad un file esistente. Non può essere utilizzato per creare un nuovo file con i dati degli oggetti.

Modifica

Aggiunta una chiamata a reset() dopo questa domanda ha mostrato che la versione originale che proprio overrode writeStreamHeader per essere un no-op potrebbe in alcune condizioni creano un flusso che non poteva essere letta.

Altri suggerimenti

Come la API dice, il costruttore ObjectOutputStream scrive l'intestazione flusso di serializzazione al flusso sottostante. E questa intestazione si pensa che sia solo una volta, all'inizio del file. Quindi chiamando

new ObjectOutputStream(fos);

più volte sul FileOutputStream che si riferisce allo stesso file scriveranno le intestazione più volte e corrompere il file.

A causa del formato preciso del file serializzato, aggiungendo sarà infatti corromperla. Devi scrivere tutti gli oggetti per il file come parte dello stesso corso d'acqua, altrimenti è destinato a bloccarsi quando legge i metadati flusso quando si aspetta un oggetto.

Si potrebbe leggere il serializzazione Specification per maggiori dettagli, o (più facile) leggere questa discussione dove Roedy verde dice fondamentalmente ciò che ho appena detto.

Il modo più semplice per evitare questo problema è quello di mantenere l'OutputStream aperto quando si scrivono i dati, invece di chiudere dopo ogni oggetto. Chiamando reset() potrebbe essere consigliabile per evitare una perdita di memoria.

L'alternativa sarebbe quella di leggere il file come una serie di ObjectInputStreams consecutivi pure. Ma questo richiede di tenere il conto quanti byte di leggere (questo può essere implementd con un FilterInputStream), quindi chiudere l'InputStream, riaprirlo, saltare che molti byte e solo allora avvolgerlo in un ObjectInputStream ().

Come circa prima di ogni volta che si aggiunge un oggetto, leggere e copiare tutti i dati correnti nel file e quindi sovrascrivere tutti insieme in un file.

Ho esteso la soluzione accettato di creare una classe che può essere utilizzato sia per il collegamento e la creazione di nuovi file.

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

}

Questa classe può essere utilizzato in sostituzione estesa diretta per ObjectOutputStream. Siamo in grado di utilizzare la classe come segue:

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

    }

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top