Anfügen an ein Object
-
19-09-2019 - |
Frage
Ist es nicht möglich, eine ObjectOutputStream
anhängen?
Ich versuche, eine Liste von Objekten anzuhängen. Folgender Ausschnitt ist eine Funktion, die aufgerufen wird, wenn ein Auftrag abgeschlossen 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
.
Zum 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 werden, so lese ich bin während es keine Ausnahmen geben. Von dem, was Google sagt, dass dies nicht möglich ist. Ich frage mich, ob jemand eine Möglichkeit kennt?
Lösung
Hier ist der Trick: Unterklasse ObjectOutputStream
und überschreibt 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();
}
}
Um es zu benutzen, überprüfen gerade, ob die Protokolldatei vorhanden ist oder nicht, und instantiate entweder diese appendable Strom (falls die Datei existiert = wir append = wir wollen nicht einen Header) oder den ursprünglichen Strom (falls die Datei wird nicht vorhanden = brauchen wir einen Header).
Bearbeiten
war ich nicht zufrieden mit der ersten Nennung der Klasse. 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 in eine vorhandene Datei zum Anhängen. Es kann nicht verwendet werden, um zu erstellen neue Datei mit Objektdaten.
Bearbeiten
einen Aufruf an reset()
Hinzugefügt nach dieser Frage zeigt , dass die Original-Version, dass nur overrode writeStreamHeader
einen no-op sein könnte unter bestimmten Bedingungen zu schaffen, um einen Strom, der nicht gelesen werden kann.
Andere Tipps
Als API sagt, schreibt der ObjectOutputStream
Konstruktor die Serialisierung Stream-Header an den zugrunde liegenden Stream. Und dieser Header wird erwartet, dass nur ein einziges Mal sein, am Anfang der Datei. 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.
Durch die genaue Format der serialisierten Datei angehängt wird es in der Tat beschädigt. Sie müssen alle Objekte in der Datei als Teil des gleichen Strom schreiben, sonst wird es abstürzen, wenn es den Stream Metadaten liest, wenn es ein Objekt erwartet.
Sie könnten lesen Sie die Serialisierung Spezifikation um weitere Informationen, oder (einfacher) lesen dieses Thema wo Roedy Grün sagt im Grunde, was ich gerade gesagt habe.
Der einfachste Weg, um dieses Problem zu vermeiden, ist den Output offen zu halten, wenn Sie die Daten schreiben, anstatt sie nach jedem Objekt zu schließen. Der Aufruf reset()
könnte sinnvoll sein, einen Speicherverlust zu vermeiden.
Die Alternative wäre die Datei als eine Reihe von aufeinanderfolgenden ObjectInputStreams als auch zu lesen. Aber dies erfordert, dass Sie immer zählen, wie viele Bytes Sie lesen (dies mit einem Filterinput sein implementd kann), dann schließen die INPUTSTREAM- es wieder öffnen, überspringen, dass viele Bytes und nur dann wickeln Sie es in einem Object ().
Wie wäre es vor jedem Mal, wenn Sie ein Objekt anhängen, lesen und kopieren Sie alle aktuellen Daten in der Datei und dann alle zusammen Datei überschreiben.
Ich habe die akzeptierte Lösung erweitert, um eine Klasse zu erstellen, die sowohl für Anfügen und die Schaffung neue Datei verwendet werden kann.
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);
}
}
}
Diese Klasse kann als direkter Ersatz für erweiterte Object verwendet werden. Wir können die Klasse wie folgt verwenden:
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();
}
}
}