Java序列化如何解决循环引用问题?

时间:2018-10-13 11:35:09

标签: java serialization

我用Java进行了序列化测试。我发现Java序列化可以正确处理循环引用。但是Java序列化如何解决循环引用问题?

以下代码正常工作:

public class SerializableTest {

    static class Employee implements Serializable{

        private static final long serialVersionUID = 1L;

        String name;

        int age;

        Employee leader;

        public void say(){
            System.out.println("my name is " + name + ". and I'm " + age + " years old.");
        }

    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File("tempPath")));

        Employee employee = new Employee();
        employee.name = "Tom";
        employee.age = 41;
        employee.leader = employee;
        employee.say();

        objectOutput.writeObject(employee);

        ObjectInput objectInput = new ObjectInputStream(new FileInputStream(new File("tempPath")));

        Employee readEmployee = (Employee) objectInput.readObject();

        readEmployee.say();
        readEmployee.leader.say();
    }
}

3 个答案:

答案 0 :(得分:2)

Java序列化使用IdentityHashMap将它尝试序列化的每个引用映射为一个id。第一次序列化对象时,它将写入其内容和ID。之后,它只写允许循环引用的id和一个对象的一个​​副本,无论它被引用了多少次。

不利之处在于,如果保留对象流而不调用reset(),它将保留您曾经发送的每个对象,从而导致内存使用量增加。同样,如果您更改了一个对象然后再次发送,则更改不会很明显,因为它仅将引用再次发送给该对象。

答案 1 :(得分:0)

在Java序列化中,循环引用有两个关键方面:反向引用和构造嵌套。

Java序列化执行深度优先遍历。考虑这个例子。

class Outer implements java.io.Serializable {
    Inner inner;
}

一个对象包含另一个对象(假设inner不为空)。外部对象开始被写入流,中间将写入内部对象,随后是其余外部对象。同样用于阅读。 Java序列化不会延迟外部对象的写入,直到内部对象被干净地构造之后。

一个类似于普通Java代码的方法是在外部对象的构造函数中构造嵌套对象。

    // Like Java Serialization
    Outer() {
        this.inner = new Inner();
    }

而不是构造嵌套对象并将引用传递给外部对象的构造函数。

    // Not like Java Serialization
    Outer(Inner inner) {
        this.inner = inner;
    }
}

    ... new Outer(new Inner()) ...

即使您只想要对象的有向无环图,也需要反向引用。考虑一个简单的例子。

class Foo implements java.io.Serializable {
    Object a = new SerialBar();
    Object b = a;
}

对于任何反序列化的实例foofoo.a == foo.b,我们应该找到。为了实现此序列化,请检查流之前是否已开始序列化引用,如果是,则插入反向引用而不是重新序列化对象。构造该对象后,该对象会被记住,但是在默认字段序列化或readObject / readExternal开始之前。

将这两件事放在一起,我们看到嵌套的对象可以接收对外部对象的引用。请注意,嵌套对象会看到部分构造的外部对象,并带来所有的乐趣。

答案 2 :(得分:0)

我在“ CoreJava®Volume II-Advanced Features,Tenth Edition” 一书中找到了答案。在第2.4节“对象输入/输出流和序列化” 这本书的描述是:

  

在幕后,ObjectOutputStream会查看对象的所有字段并保存其内容。例如,在编写Employee对象时,姓名,日期和薪水字段将写入输出流。

     

但是,我们需要考虑一种重要的情况:当一个对象被多个对象共享作为其状态的一部分时会发生什么?

     

保存这样的对象网络是一个挑战。当然,我们不能保存和恢复秘书对象的内存地址。重新加载对象时,它可能会占用与原始地址完全不同的内存地址。

     

相反,每个对象都保存有序列号,因此名称对象序列化    对于这种机制。这是算法:

     
      
  1. 将序列号与您遇到的每个对象引用相关联(如图2.6所示)。

  2.   
  3. 首次遇到对象引用时,请将对象数据保存到输出流。

  4.   
  5. 如果先前已保存,只需输入“与先前保存的序列号x相同”。

  6.   
     

读回对象时,过程相反。

     
      
  1. 在对象输入流中首次指定对象时,对其进行构造,并使用流数据对其进行初始化,并记住序列号与对象引用之间的关联。

  2.   
  3. 遇到标签“与先前保存的序列号为x的对象相同”时,请检索序列号的对象引用。

  4.   

因此,可以使用JAVA以这种方式序列化和反序列化对象图。

相关问题