7

さまざまな理由から、いくつかのかなり単純なオブジェクトをデータ ファイルにダンプするカスタム シリアライゼーションがあります。おそらく 5 ~ 10 個のクラスがあり、結果として得られるオブジェクト グラフは非循環的で非常に単純です (シリアル化された各オブジェクトには、シリアル化された別のオブジェクトへの 1 つまたは 2 つの参照があります)。例えば:

class Foo
{
    final private long id;
    public Foo(long id, /* other stuff */) { ... }
}

class Bar
{
    final private long id;
    final private Foo foo;
    public Bar(long id, Foo foo, /* other stuff */) { ... }
}

class Baz
{
    final private long id;
    final private List<Bar> barList;
    public Baz(long id, List<Bar> barList, /* other stuff */) { ... }
}

id フィールドはシリアル化のためだけにあるため、ファイルにシリアル化するときに、これまでにシリアル化された ID の記録を保持することでオブジェクトを書き込むことができます。次に、オブジェクトごとに、その子オブジェクトがシリアル化されているかどうかを確認して書き込みます。そうでないものは、データ フィールドとその子オブジェクトに対応する ID を書き込むことによって、最終的にオブジェクト自体を書き込みます。

私を困惑させているのは、IDを割り当てる方法です。考えてみると、ID の割り当てには次の 3 つのケースがあるようです。

  • 動的に作成されたオブジェクト -- ID は増分するカウンターから割り当てられます
  • ディスクからのオブジェクトの読み取り -- ディスク ファイルに格納されている番号から ID が割り当てられます
  • シングルトン オブジェクト -- オブジェクトは、常に存在するシングルトン オブジェクトを表すために、動的に作成されるオブジェクトの前に作成されます。

これらを適切に処理するにはどうすればよいですか?私は車輪を再発明しているように感じ、すべてのケースを処理するための十分に確立された技術が必要です.


明確化:いくつかの接線情報と同様に、私が見ているファイル形式はおおよそ次のとおりです(関連するべきではないいくつかの詳細をざっと説明します)。かなり大量の高密度バイナリ データ (数十/数百 MB) を処理するように最適化されており、その中に構造化データを散在させることができます。密なバイナリ データは、ファイル サイズの 99.9% を占めます。

このファイルは、コンテナーとして機能する一連のエラー修正ブロックで構成されています。各ブロックは、一連のパケットで構成されるバイト配列を含むと考えることができます。パケットを一度に 1 つずつ連続して読み取ることができます (たとえば、各パケットの終わりがどこにあるかを知ることができ、その直後に次のパケットが開始されます)。

したがって、ファイルは、エラー修正レイヤーの上に格納された一連のパケットと考えることができます。これらのパケットの大部分は、この質問とは関係のない不透明なバイナリ データです。ただし、これらのパケットのごく一部は、シリアル化された構造化データを含むアイテムであり、オブジェクト参照関係によってリンクされる可能性のあるデータ「島」で構成される一種の「群島」を形成します。

したがって、パケット 2971 にシリアル化された Foo が含まれ、パケット 12083 にパケット 2971 の Foo を参照するシリアル化された Bar が含まれるファイルがあるとします (パケット 0 ~ 2970 および 2972​​ ~ 12082 は不透明なデータ パケットです)。

これらのパケットはすべて不変です (したがって、Java オブジェクト構築の制約があるため、非循環オブジェクト グラフを形成します)。そのため、可変性の問題に対処する必要はありません。これらは、共通Itemインターフェースの子孫でもあります。私がやりたいことは、任意のItemオブジェクトをファイルに書き込むことです。Itemに既にファイルにある他の への参照が含まれている場合、Itemそれらもファイルに書き込む必要がありますが、まだ書き込まれていない場合に限ります。そうしないと、それらを読み返すときに何らかの形で合体する必要がある重複が発生します。

4

3 に答える 3

4

本当にこれを行う必要がありますか? 内部的には、ObjectOutputStreamどのオブジェクトが既にシリアル化されているかを追跡します。同じオブジェクトの後続の書き込みは、オブジェクト全体を再度書き出すのではなく、内部参照のみを格納します (id だけを書き出すのと同様)。

詳細については、シリアル化キャッシュを参照してください。

ID が、エンティティ ID などの外部で定義された ID に対応している場合、これは理にかなっています。しかし、質問は、どのオブジェクトがシリアル化されているかを追跡するためだけに ID が生成されると述べています。

readResolveメソッドを介してシングルトンを処理できます。簡単な方法は、デシリアライズされたばかりのインスタンスをシングルトン インスタンスと比較し、一致する場合は、デシリアライズされたインスタンスではなくシングルトン インスタンスを返すことです。例えば

   private Object readResolve() {
      return (this.equals(SINGLETON)) ? SINGLETON : this;
      // or simply
      // return SINGLETON;
   }

編集: コメントに応じて、ストリームはほとんどがバイナリ データ (最適化された形式で格納されている) であり、そのデータには複雑なオブジェクトが分散しています。これは、zip などのサブストリームをサポートするストリーム形式、または単純なブロック チャンクを使用して処理できます。たとえば、ストリームは一連のブロックにすることができます:

offset 0  - block type
offset 4  - block length N
offset 8  - N bytes of data
...
offset N+8  start of next block

次に、バイナリ データ用のブロック、シリアル化されたデータ用のブロック、XStream シリアル化されたデータ用のブロックなどを作成できます。各ブロックはサイズを認識しているため、サブストリームを作成して、ファイル内の場所からその長さまで読み取ることができます。これにより、解析を気にせずに自由にデータを混在させることができます。

ストリームを実装するには、メイン ストリームにブロックを解析させます。

   DataInputStream main = new DataInputStream(input);
   int blockType = main.readInt();
   int blockLength = main.readInt();
   // next N bytes are the data
   LimitInputStream data = new LimitInputStream(main, blockLength);

   if (blockType==BINARY) {
      handleBinaryBlock(new DataInputStream(data));
   }
   else if (blockType==OBJECTSTREAM) {
      deserialize(new ObjectInputStream(data));
   }
   else
      ...

のスケッチはLimitInputStream次のようになります。

public class LimitInputStream extends FilterInputStream
{
   private int bytesRead;
   private int limit;
   /** Reads up to limit bytes from in */
   public LimitInputStream(InputStream in, int limit) {
      super(in);
      this.limit = limit;
   }

   public int read(byte[] data, int offs, int len) throws IOException {
      if (len==0) return 0; // read() contract mandates this
      if (bytesRead==limit)
         return -1;
      int toRead = Math.min(limit-bytesRead, len);
      int actuallyRead = super.read(data, offs, toRead);
      if (actuallyRead==-1)
          throw new UnexpectedEOFException();
      bytesRead += actuallyRead;
      return actuallyRead;
   }

   // similarly for the other read() methods

   // don't propagate to underlying stream
   public void close() { }
}
于 2010-06-08T16:23:53.843 に答える
1

私は車輪の再発明をしているような気がします。すべてのケースを処理するための確立された技術が必要です。

はい、デフォルトのオブジェクトシリアル化が行うように見えます。または、最終的には事前に最適化しています。

シリアル化されたデータの形式(XMLEncoderのように)を変更して、より便利なものにすることができます。

しかし、あなたが主張するなら、動的カウンターを備えたシングルトンはそうすべきだと思いますが、コンストラクターのパブリックインターフェイスにIDを入れないでください:

class Foo {
    private final int id;
    public Foo( int id, /*other*/ ) { // drop the int id
    }
 }

したがって、クラスは「シーケンス」である可能性があり、。で問題が発生しないようにするには、おそらくlongの方が適切Integer.MAX_VALUEです。

java.util.concurrent.atomicAtomicLongパッケージで説明されているように(2つのスレッドに同じIDを割り当てないようにするため、または過度の同期を避けるため)を使用することも役立ちます。

class Sequencer {
    private static AtomicLong sequenceNumber = new AtomicLong(0);
    public static long next() { 
         return sequenceNumber.getAndIncrement();
    }
}

今、あなたは各クラスにいます

 class Foo {
      private final long id;
      public Foo( String name, String data, etc ) {
          this.id = Sequencer.next();
      }
 }

以上です。

(オブジェクトを逆シリアル化するとコンストラクターが呼び出されるかどうかは覚えていませんが、アイデアは得られます)

于 2010-06-08T18:16:09.080 に答える
1

foo は FooRegistry に登録されていますか? このアプローチを試すことができます (Bar と Baz にも ID を介して参照を取得するためのレジストリがあると仮定します)。

これにはおそらく多くの構文エラー、使用エラーなどが含まれます。しかし、アプローチは良いものだと思います。

公開クラス Foo {

public Foo(...) {
    //construct
    this.id = FooRegistry.register(this);
}

public Foo(long id, ...) {
    //construct
    this.id = id;
    FooRegistry.register(this,id);
}

}

public class FooRegistry() { Map foos = new HashMap...

long register(Foo foo) {
    while(foos.get(currentFooCount) == null) currentFooCount++;
    foos.add(currentFooCount,foo);
    return currentFooCount;
}

void register(Foo foo, long id) {
    if(foo.get(id) == null) throw new Exc ... // invalid
    foos.add(foo,id);
}

}

public class Bar() {

void writeToStream(OutputStream out) {
    out.print("<BAR><id>" + id + "</id><foo>" + foo.getId() + "</foo></BAR>");
}

}

public class Baz() {

void.writeToStream(OutputStream out) {
    out.print("<BAZ><id>" + id + "</id>");
    for(Bar bar : barList) out.println("<bar>" + bar.getId() + </bar>");
    out.print("</BAZ>");
}

}

于 2010-06-08T14:29:42.907 に答える