48

Ist es nicht möglich, an eine ObjectOutputStream anzuhängen?Anhängen an einen ObjectOutputStream

Ich versuche, an eine Liste von Objekten anzuhängen. Das folgende Snippet ist eine Funktion, die immer dann aufgerufen wird, wenn ein Job beendet ist.

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

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

Aber wenn ich versuche, es zu lesen, bekomme ich nur die erste in der Datei. Dann bekomme ich java.io.StreamCorruptedException.

zu lesen Ich bin mit

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

Ich weiß nicht, wie viele Objekte vorhanden sein wird, um das Lesen ich während es keine Ausnahmen gibt. Von dem, was Google sagt, ist das nicht möglich. Ich habe mich gefragt, ob jemand einen Weg kennt?

+0

Erhalten Sie überhaupt etwas aus dem Stream, oder wirft es beim ersten Mal eine Ausnahme? – skaffman

+0

es liest das erste Objekt, das ich gespeichert habe, dann bekomme ich eine Ausnahme. –

+0

Im obigen Code sehe ich nur ein Objekt in der Datei geschrieben, so dass nur einer gelesen werden würde, oder? – aperkins

Antwort

67

Hier ist der Trick: Unterklasse ObjectOutputStream und überschreiben die writeStreamHeader Methode:

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

} 

es zu benutzen, nur prüfen, ob die History-Datei oder nicht, und instanziiert existiert entweder diesen appendable Strom (im Fall existiert die Datei = wir append = wir wollen keinen Header) oder den ursprünglichen Stream (falls die Datei nicht existiert = wir brauchen einen Header).

bearbeiten

war ich mit der ersten Nennung der Klasse nicht glücklich. Dieser ist besser: es beschreibt das ‚was es für‘ eher dann das ‚wie es gemacht wird‘

bearbeiten

änderte den Namen noch einmal, zu klären, dass dieser Strom nur zu einem bestehenden zum Anhängen Datei.Es kann nicht verwendet werden, um eine neue Datei mit Objektdaten zu erstellen.

bearbeiten

Added ein Aufruf an reset() nach this question zeigte, dass die ursprüngliche Version, die nur writeStreamHeader ein no-op konnte unter bestimmten Bedingungen einen Strom erzeugen sein, überwog die nicht gelesen werden konnte.

+0

Clever! Ich denke * der Stream-Header ist das einzige Problem, in diesem Fall sollte das wunderbar funktionieren, aber hast du es getestet, um sicher zu sein? –

+0

Danke für deine Kommentare :-) Ja, ich habe einen getesteten Code geschrieben (vergaß es einfach in der Antwort zu erwähnen) –

+0

+1, danke. * Warnung: * writeStreamHeader() wird vom ObjectOutputStream * -Konstruktor * aufgerufen. Dies macht es zumindest für eine einzelne Klasse schwierig, beide Fälle zu behandeln. Sie können eine einfache Factory-Methode erstellen, die fileExists zurückgibt. new AppendingObjectOutputStream (out): new ObjectOutputStream (out); –

7

Aufgrund des genauen Formats der serialisierten Datei wird das Anhängen tatsächlich beschädigt. Sie müssen alle Objekte als Teil desselben Streams in die Datei schreiben, sonst stürzt es ab, wenn die Stream-Metadaten gelesen werden, wenn ein Objekt erwartet wird.

Sie könnten die Serialization Specification für weitere Details lesen, oder (einfacher) lesen this thread, wo Roedy Green im Grunde sagt, was ich gerade gesagt habe.

13

Wie der API sagt, schreibt der -Konstruktor den Serialisierungs-Stream-Header in den zugrunde liegenden Stream. Und dieser Header wird voraussichtlich nur einmal am Anfang der Datei sein. So rufen

new ObjectOutputStream(fos); 

mehrere Male auf der FileOutputStream, die auf die gleiche Datei bezieht, wird die Kopfzeile mehrere Male und korrupt die Datei schreiben.

5

Die einfachste Möglichkeit, dieses Problem zu vermeiden, ist, den OutputStream beim Schreiben der Daten geöffnet zu lassen, anstatt ihn nach jedem Objekt zu schließen. Rufen Sie reset() an, um ein Speicherleck zu vermeiden.

Die Alternative wäre, die Datei als eine Folge von aufeinander folgenden ObjectInputStreams zu lesen. Aber das erfordert, dass Sie die Anzahl der gelesenen Bytes zählen (dies kann mit einem FilterInputStream implementiert werden), schließen Sie dann den InputStream, öffnen Sie ihn erneut, überspringen Sie diese vielen Bytes und wickeln Sie ihn dann nur in einen ObjectInputStream().

0

Wie wäre es vor jedem Anhängen eines Objekts, lesen und kopieren Sie alle aktuellen Daten in der Datei und überschreiben Sie dann alle zusammen in der Datei.