【问题标题】:How can I link two Java serialised objects back together?如何将两个 Java 序列化对象重新链接在一起?
【发布时间】:2010-04-26 21:50:03
【问题描述】:

有时(实际上相当多)我们会在 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()));
 }

}

【问题讨论】:

  • +1 在任何语言中反序列化对象的祸根

标签: java serialization


【解决方案1】:

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

【讨论】:

  • (或许还有writeReplace。)
  • 粗略地说,我尝试这样做的原因是因为我有一个文件包含不太可能在应用程序的多个会话中更改的数据,而另一个文件包含特定于单个会话的数据的应用程序。它们之间有交叉引用,但我想让它们在逻辑上分开。
【解决方案2】:

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


如果你还是想继续,这里是如何做到的:

首先,您需要通过覆盖ObjectOutputStream.replaceObject()ObjectInputStream.resolveObject() 方法来挂钩到序列化过程。以我的ObjectSerializer 为例。

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

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

如果你想做延迟加载,为了避免在不需要时将整个对象图加载到内存中,你必须做类似transparent references的事情。查看他们的实现here。如果你已经走到了这一步,你不妨从我的项目中复制这些部分(包entitiesentities.trefserial 可能还有context)——它有一个许可许可证——并将它们修改为满足您的需求(即删除您不需要的东西)。

【讨论】:

    【解决方案3】:

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

    假设您的类实现了 hashCode() 和 equals(),您可以通过创建静态 WeakHashMap 来实现重复数据删除,该静态 WeakHashMap 将所有对已创建对象的引用保存在内存中。例如

    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,传递 this,以便将这些对象也添加到池中。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-13
      • 2013-01-24
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多