如何将两个Java序列化对象重新链接在一起?

时间:2010-04-26 21:50:04

标签: java serialization

有时候(实际上很多)我们在Java中得到一个情况,其中两个对象指向同一个东西。现在,如果我们将这些单独序列化,则序列化表单具有对象的单独副本是非常合适的,因为它应该可以打开而不使用另一个。但是,如果我们现在对它们进行反序列化,我们发现它们仍然是分开的。有没有办法将它们重新组合在一起?

示例如下。

public class Example {

 private static class ContainerClass implements java.io.Serializable {
  private ReferencedClass obj;
  public ReferencedClass get() {
   return obj;
  }
  public void set(ReferencedClass obj) {
   this.obj = obj;
  }
 }

 private static class ReferencedClass implements java.io.Serializable {
  private int i = 0;
  public int get() {
   return i;
  }
  public void set(int i) {
   this.i = i;
  }
 }

 public static void main(String[] args) throws Exception {
  //Initialise the classes
  ContainerClass test1 = new ContainerClass();
  ContainerClass test2 = new ContainerClass();
  ReferencedClass ref = new ReferencedClass();

  //Make both container class point to the same reference
  test1.set(ref);
  test2.set(ref);

  //This does what we expect: setting the integer in one (way of accessing the) referenced class sets it in the other one
  test1.get().set(1234);
  System.out.println(Integer.toString(test2.get().get()));

  //Now serialise the container classes
  java.io.ObjectOutputStream os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test1.ser"));
  os.writeObject(test1);
  os.close();
  os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test2.ser"));
  os.writeObject(test2);
  os.close();

  //And deserialise them
  java.io.ObjectInputStream is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test1.ser"));
  ContainerClass test3 = (ContainerClass)is.readObject();
  is.close();
  is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test2.ser"));
  ContainerClass test4 = (ContainerClass)is.readObject();
  is.close();

  //We expect the same thing as before, and would expect a result of 4321, but this doesn't happen as the referenced objects are now separate instances
  test3.get().set(4321);
  System.out.println(Integer.toString(test4.get().get()));
 }

}

3 个答案:

答案 0 :(得分:3)

readResolve() method允许这样做(当然,首先你必须定义你将如何决定哪些对象是“相同的”)。但是将两个对象序列化到同一个文件中要容易得多 - ObjectOut / InputStream会保存它已序列化/反序列化的所有对象的记录,并且只存储和返回它已经看到的对象的引用。

答案 1 :(得分:1)

我为我正在构建的应用程序服务器/对象数据库做了类似的事情。您有什么要求 - 为什么需要这样做?如果您的要求比应用程序服务器少,那么也许其他一些设计可以更轻松地解决它。


如果您仍想继续,请按照以下步骤操作:

首先,您需要通过覆盖ObjectOutputStream.replaceObject()ObjectInputStream.resolveObject()方法来挂钩序列化过程。有关示例,请参阅我的ObjectSerializer

序列化对象时,必须为每个要具有唯一标识的对象实例分配唯一的ID - 这种对象通常称为实体。当对象引用其他实体时,您必须使用包含引用实体ID的占位符对象替换其他实体。

然后,当反序列化对象时,必须将每个占位符对象替换为具有该ID的实际实体对象。您需要跟踪已加载到内存中的实体对象实例及其ID,以便每个ID只创建一次实例。如果实体尚未加载到内存中,则必须从保存它的位置加载它。有关示例,请参阅我的EntityManager

如果要进行延迟加载,为避免在不需要时将整个对象图加载到内存中,必须执行与transparent references类似的操作。查看他们的实施here。如果你做得那么远,你也可以从我的项目中复制这些部分(包entitiesentities.trefserial,也许还有context) - 它有许可许可 - 并根据您的需要进行修改(即删除您不需要的东西)。

答案 2 :(得分:1)

与上述答案一样,readResolve是关键,因为它允许您将“复制”对象替换为您想要的对象。

假设您的类实现了hashCode()和equals(),您可以通过创建一个静态WeakHashMap来实现重复数据删除,该静态WeakHashMap包含对仍在内存中的已创建对象的所有引用。 e.g。

class ReferencedClass implements Serializable
{
   static private Map<ReferencedClass, Reference<ReferencedClass>> map = new WeakHashMap<ReferencedClass, Reference<ReferencedClass>>;

   static public ReferencedClass findOriginal(ReferencedClass obj)
   {
      WeakReference<ReferencedClass> originalRef = map.get(obj);
      ReferencedClass original = originalRef==null ? null : originalRef.get();
      if (original==null)
      {
          original = obj;
          map.put(original, new WeakReference<ReferencedClass>(original));
      }
      return original;
   }

   static public ReferencedClass()
   {
        findOriginal(this);
   }

   private Object readResolve()
   {
       return findOriginal(this);
   }
}

反序列化时,readResolve()调用RerencedClass.findOriginal(this)来获取当前原始实例。如果此类的实例仅通过反序列化创建,那么这将按原样工作。如果你也在构造对象(使用new运算符),那么你的构造函数也应该调用findOriginal,传递它,这样那些对象也会被添加到池中。

有了这些更改后,两个ContainerClass实例都会指向同一个ReferenceClass实例,即使它们是独立反序列化的。