我用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();
}
}
答案 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;
}
对于任何反序列化的实例foo
,foo.a == foo.b
,我们应该找到。为了实现此序列化,请检查流之前是否已开始序列化引用,如果是,则插入反向引用而不是重新序列化对象。构造该对象后,该对象会被记住,但是在默认字段序列化或readObject
/ readExternal
开始之前。
将这两件事放在一起,我们看到嵌套的对象可以接收对外部对象的引用。请注意,嵌套对象会看到部分构造的外部对象,并带来所有的乐趣。
答案 2 :(得分:0)
我在“ CoreJava®Volume II-Advanced Features,Tenth Edition” 一书中找到了答案。在第2.4节“对象输入/输出流和序列化” 这本书的描述是:
在幕后,ObjectOutputStream会查看对象的所有字段并保存其内容。例如,在编写Employee对象时,姓名,日期和薪水字段将写入输出流。
但是,我们需要考虑一种重要的情况:当一个对象被多个对象共享作为其状态的一部分时会发生什么?
保存这样的对象网络是一个挑战。当然,我们不能保存和恢复秘书对象的内存地址。重新加载对象时,它可能会占用与原始地址完全不同的内存地址。
相反,每个对象都保存有序列号,因此名称对象序列化 对于这种机制。这是算法:
将序列号与您遇到的每个对象引用相关联(如图2.6所示)。
首次遇到对象引用时,请将对象数据保存到输出流。
如果先前已保存,只需输入“与先前保存的序列号x相同”。
读回对象时,过程相反。
在对象输入流中首次指定对象时,对其进行构造,并使用流数据对其进行初始化,并记住序列号与对象引用之间的关联。
遇到标签“与先前保存的序列号为x的对象相同”时,请检索序列号的对象引用。
因此,可以使用JAVA以这种方式序列化和反序列化对象图。