6

Javaのシリアル化ライブラリからStackOverflowErrorsを取得しています。問題は、デフォルトのシリアル化の実装が再帰的であり、その深さが参照のネットワークを通る最長のパスによってのみ制限されることです。

デフォルトのメソッドをオーバーライドできることはわかっていますが、プロジェクトには何百もの豊富に接続されたクラスがあるため、オーバーライドのアプローチには熱心ではありません。非再帰的である(または少なくとも再帰をスタックからヒープに移動する)一般化されたソリューションがある場合は、より関心があります。

私はこのトピックをグーグルで検索し、同じことについてひどく不平を言っている人はたくさんいるだけでしたが、これらの不満のほとんどは何年も前のものでした。状況は改善しましたか?そうでない場合、一般化された実装を作成しますが、何かアドバイスはありますか?誰もこのナッツを割っていない理由があると推測しています(まだ明らかではありません)。理論的には、それを「正しく」行うことは実行可能であるはずのように聞こえます。

4

4 に答える 4

2

私はしばらく前にこの問題を抱えていました。豊富に接続されたクラスの場合、スタックオーバーフローなしでシリアル化を完了できたとしても、シリアル化は非常に遅くなります。この問題を解決したとき、いくつかのクラスがあったので、データを整数のオブジェクトIDのセットにパックし、各フィールドに整数のフィールドIDを設定し、一連のオブジェクトIDを介してそれらの接続を記述する独自のシリアル化形式を作成しました。 、フィールドID、その他のオブジェクトIDマッピング。このカスタムアプローチは非常に高速で、メモリを非常に軽くしましたが、実際には、シリアル化するクラスのセットが少ない場合にのみ機能します。

一般的なケースははるかに難しく、豊富に接続されたクラスのシリアル化の需要はそれほど強くないので、誰もそれを解決しなかったのはそのためだと思います。

基本的にはすでに問題に取り組んでいますが、常に深さ優先探索ツリーの最大高さに等しいスタック深度が必要になるため、グラフがそれよりも深い場合は常にスタックオーバーフローが発生します。これは基本的に再帰的な問題であるため、スタック割り当てをヒープに配置したStackオブジェクトに移動して、再帰または偽の再帰を使用する必要があります。OpenJDKの実装を見てみましょう。

http://hg.openjdk.java.net/jdk6/jdk6-gate/jdk/file/tip/src/share/classes/java/io/ObjectOutputStream.java

すでにDebugTraceInfoStackがあります。現在作成しているオブジェクトに対して、2番目のStackフィールドを作成し、writeObject0メソッドを変更して、オブジェクトをスタックにプッシュします。次のようになります。

stack.push(obj);
while(!stack.empty()) {
    obj = stack.pop();
    ...

次に、writeObject0(x)へのすべての呼び出しを変更します。stack.push(x);に。クラスがほぼ2500行であり、おそらく大量の落とし穴があることを除いて、再帰と反復の間の単純で標準的な変換。

ただし、最終的にビルドする場合は、ディープオブジェクトグラフで使用するIterativeObjectOutputStreamのような、次のバージョンのJavaへのパッチとして送信することをお勧めします。

于 2011-09-16T05:57:28.670 に答える
1

JDK6シリアル化が再帰オブジェクトグラフを処理できることの証明:

public static void main(String[] args) throws Exception {
    Foo foo = new Foo("bob");
    foo.setBar(new Bar("fred", foo));
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(baos);
    out.writeObject(foo);
    out.close();
    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
    Object o = in.readObject();
    System.out.println(o);
}

static class Foo implements Serializable {
    String name;
    Bar bar;

    Foo(String name) {
        this.name = name;
    }

    void setBar(Bar bar) {
        this.bar = bar;
    }

    @Override
    public String toString() {
        return "Foo{" +
                "name='" + name + '\'' +
                ", bar=" + bar +
                '}';
    }
}

static class Bar implements Serializable {
    String name;
    Foo foo;

    Bar(String name, Foo foo) {
        this.name = name;
        this.foo = foo;
    }

    @Override
    public String toString() {
        return "Bar{" +
                "name='" + name + '\'' +
                '}';
    }
}

出力:

Foo{name='bob', bar=Bar{name='fred'}}
于 2011-09-16T02:53:24.403 に答える
0

質問をよく読んでいないようです。循環参照を含む可能性のあるプロパティのシリアル化に関心があるようです。この仮定が正しくなく、循環参照を含むこれらのオブジェクトをシリアル化しないことで問題がない場合は、以下の私の元の回答を参照してください。

新しい答え

どのオブジェクトがシリアル化されているかを追跡する必要があると思います。自分で実行しない限り、これが発生していることはわかりません。しかし、それはそれほど難しいことではないはずです。

循環参照を含むこれらのオブジェクトではtransient boolean、オブジェクトがすでにシリアル化されているかどうかを表すことができます。次に、デフォルトのシリアル化動作をオーバーライドする必要がありますが、これは数行で実行できます。

public void writeExternal(ObjectOutput out) {
    if(!out.serialized) {
        out.serializeMethod();
    }
    out.serialized = true;
}

元の回答

キーワードを見てくださいtransient

transientほとんどのシリアル化ライブラリがキーワードを尊重すると思います。メンバーである場合、transientそれはシリアル化から除外されることを意味します。

class Something {
    private Dog dog; // I will be serialized upon serialization.
    private transient SomethingElse somethingElse; // I will not be serialized upon serialization.
}

class SomethingElse {
    private Cat cat; // I will be serialized upon serialization.
    private transient Something something; // I will not be serialized upon serialization.
}

上記のシナリオと同様の再帰メンバーがある場合はtransient、このオーバーフローが発生しないように、一方または他方(または両方)にマークを付ける必要があります。

于 2011-09-16T01:58:49.157 に答える
0

GWT RPCシリアル化は基本的にJVMシリアル化と同等であり、どちらもスタック/再帰的手法を使用します。残念ながら、これは作業をチャンクにスライスする場合にはうまく機能しません(ブラウザーで作業している場合、つまりGWTで作業している場合はこれを行う必要があります)。そのため、非再帰的なアプローチを次に示します: https ://github.com/nevella/ alcina / blob / d3e37df57709620f7ad54d3d59b997e9c4c7d883 / extras / rpc / client / src / com / google / gwt / user / client / rpc / impl / ClientSerializationStreamReader.java

基本的に、シリアル化を3つのパスに変換します。*オブジェクトをインスタンス化します*プロパティを設定します(リンクを介して)*コレクションにデータを入力します

2つのトリック:インスタンス化時にプロパティが必要なオブジェクト(日付など)と、メンバーのハッシュが必要になる可能性があるため、コレクションに最後にデータを入力する必要があるオブジェクトがあります。

これにより、非再帰的な逆シリアル化が可能になりますが、実際には、非再帰的なシリアル化はさらに単純であり(カスタムのwriteReplace / readResolveがない限り)、writeObject内でオブジェクトの2つのキュー(シリアル化されていない、プロパティはシリアル化されていない)を維持します-for-current-objectであり、再帰呼び出しを行うのではなく、serialize-object-propertyをマーカーを使用してスタックにプッシュします。

ここにその非常に基本的な例があります: http ://www.w3.org/2006/02/Sierra10022006.pdf

于 2015-05-20T06:11:02.593 に答える