java:为自定义序列化分配对象引用ID

Jas*_*n S 6 java serialization object-graph

由于各种原因,我有一个自定义序列化,我将一些相当简单的对象转储到数据文件.可能有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 */) { ... }
}
Run Code Online (Sandbox Code Playgroud)

id字段仅用于序列化,因此当我序列化到文件时,我可以通过保存到目前为止序列化了哪些ID的记录来编写对象,然后为每个对象检查其子对象是否已被序列化并写入那些没有的,最后通过编写数据字段和与其子对象相对应的ID来编写对象本身.

令我困惑的是如何分配id.我想到了,似乎有三种情况可以分配ID:

  • 动态创建的对象 - 从递增的计数器分配id
  • 从磁盘读取对象 - 从磁盘文件中存储的编号分配id
  • singleton对象 - 在任何动态创建的对象之前创建对象,以表示始终存在的单个对象.

我该如何妥善处理?我觉得我正在重新发明轮子,必须有一套完善的技术来处理所有情况.


澄清:就像一些切向信息一样,我正在查看的文件格式大致如下(掩盖了一些不应该相关的细节).它经过优化处理相当大量的密集二进制数据(数十/数百MB),并能够在其中散布结构化数据.密集的二进制数据占文件大小的99.9%.

该文件由一系列纠正错误的块组成,这些块用作容器.可以认为每个块包含由一系列包组成的字节数组.可以一次一个地读取一个数据包(例如,可以告诉每个数据包的结束位置,然后下一个数据包立即开始).

因此,该文件可以被认为是存储在纠错层之上的一系列分组.绝大多数这些数据包都是不透明的二进制数据,与此问题无关.然而,这些数据包中的少数是包含序列化结构化数据的项目,形成一种由数据"岛"组成的"群岛",其可以通过对象参考关系链接.

所以我可能有一个文件,其中包2971包含序列化的Foo,而包12083包含一个序列化的Bar,它引用包2971中的Foo.(包0-2970和2972-12082是不透明的数据包)

所有这些数据包都是不可变的(因此给定Java对象构造的约束,它们形成一个非循环对象图),所以我不必处理可变性问题.它们也是共同Item界面的后代.我想做的是将任意Item对象写入文件.如果Item包含对Item文件中已有的其他s的引用,我也需要将它们写入文件,但前提是它们尚未编写.否则我会有重复,当我读回它时,我需要以某种方式合并.

mdm*_*dma 4

你真的需要这样做吗?在内部,ObjectOutputStream跟踪哪些对象已经被序列化。同一对象的后续写入仅存储内部引用(类似于仅写出 id),而不是再次写出整个对象。

请参阅序列化缓存有关更多详细信息,

如果 ID 对应于某些外部定义的身份(例如实体 ID),那么这是有意义的。但问题指出,生成 ID 纯粹是为了跟踪哪些对象被序列化。

您可以通过该方法处理单例readResolve。一种简单的方法是将新反序列化的实例与单例实例进行比较,如果匹配,则返回单例实例而不是反序列化的实例。例如

   private Object readResolve() {
      return (this.equals(SINGLETON)) ? SINGLETON : this;
      // or simply
      // return SINGLETON;
   }
Run Code Online (Sandbox Code Playgroud)

编辑:为了响应评论,流主要是二进制数据(以优化格式存储),复杂对象分散在该数据中。这可以通过使用支持子流的流格式(例如 zip 或简单的块分块)来处理。例如,流可以是一系列块:

offset 0  - block type
offset 4  - block length N
offset 8  - N bytes of data
...
offset N+8  start of next block
Run Code Online (Sandbox Code Playgroud)

然后,您可以拥有二进制数据块、序列化数据块、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
      ...
Run Code Online (Sandbox Code Playgroud)

草图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() { }
}
Run Code Online (Sandbox Code Playgroud)