web-dev-qa-db-ja.com

ObjectOutputStreamに追加する

ObjectOutputStreamに追加することはできませんか?

オブジェクトのリストに追加しようとしています。次のスニペットは、ジョブが終了するたびに呼び出される関数です。

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

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

しかし、それを読み込もうとすると、ファイルの最初のものしか取得できません。その後、Java.io.StreamCorruptedException

読むために私は使用しています

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

いくつのオブジェクトが存在するかわからないので、例外はありませんが読んでいます。 Googleによると、これは不可能です。誰かが方法を知っているのだろうかと思っていましたか?

55
Hamza Yerlikaya

トリックは次のとおりです:ObjectOutputStreamをサブクラス化し、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();
  }

}
_

それを使用するには、単に履歴ファイルが存在するかどうかを確認し、この追加可能なストリーム(ファイルが存在する場合=ヘッダーを追加しない場合)または元のストリーム(ファイルが存在しない場合=ヘッダーが必要です)。

編集

クラスの最初の命名に満足できませんでした。これは優れています。「方法」ではなく「目的」を説明します

編集

このストリームは既存のファイルに追加するためだけのものであることを明確にするために、名前をもう一度変更しました。オブジェクトデータを使用してnewファイルを作成するために使用することはできません。

編集

この質問 の後にreset()への呼び出しを追加しました。writeStreamHeaderを単に無効にした元のバージョンは、特定の条件下で、no-opになることができなかったストリームを作成できることを示しました。読んでください。

77
Andreas_D

[〜#〜] api [〜#〜] が示すように、ObjectOutputStreamコンストラクターは、シリアル化ストリームヘッダーを基になるストリームに書き込みます。また、このヘッダーは、ファイルの先頭で1回だけであると予想されます。だから

new ObjectOutputStream(fos);

同じファイルを参照するFileOutputStreamを複数回使用すると、ヘッダーが複数回書き込まれ、ファイルが破損します。

13
Tadeusz Kopec

シリアル化されたファイルは正確な形式であるため、追加すると実際に破損します。同じストリームの一部としてすべてのオブジェクトをファイルに書き込む必要があります。そうしないと、オブジェクトを予期しているときにストリームメタデータを読み取るときにクラッシュします。

詳細については シリアル化仕様 を読むか、(より簡単に) このスレッド を読んでください。

7
Michael Myers

この問題を回避する最も簡単な方法は、データを書き込むときに、各オブジェクトの後に閉じるのではなく、OutputStreamを開いたままにしておくことです。メモリリークを避けるため、reset()を呼び出すことをお勧めします。

別の方法は、ファイルを一連の連続したObjectInputStreamsとして読み取ることです。ただし、これには、読み込むバイト数をカウントし続ける必要があり(FilterInputStreamで実装できます)、InputStreamを閉じて再度開き、そのバイト数をスキップしてからObjectInputStream()でラップするだけです。

6

オブジェクトを追加するたびに、ファイル内のすべての現在のデータを読み取ってコピーしてから、すべてをファイルに上書きする前にどうでしょうか。

0
user4147874

受け入れられたソリューションを拡張して、新しいファイルの追加と作成の両方に使用できるクラスを作成しました。

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

}

このクラスは、ObjectOutputStreamの直接拡張置換として使用できます。次のようにクラスを使用できます。

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

    }

}
0
Pratanu Mandal