序列化
什么是序列化?
当你创建对象时,只有你需要,它就会一直存在,但是在程序终止时,无论如何他都不会在内存中存活。尽管这是有意义的,但是如果能将一个对象剥离到硬盘中,进行持久化存储,这也是很有意义的。Java提供了这么一个功能,就是序列化。
如何序列化?
在Java中,提供了两个接口来进行序列化操作。
1. Serializable 接口
这个接口是java中的一个标记接口,实现这个接口的类会被java识别为可以被序列化的类。
public class A implements Serializable {
private String a;
private String b;
private int c;
A(String a, String b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
public String toString() {
return a + ", "+ b + ", " + c;
}
}
public static void main(String[] args) throws Exception {
A a = new A("1", "2", 3);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
out.writeObject(a);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
A a1 = (A)in.readObject();
System.out.println(a1);
}
输出结果为:
1, 2, 3
这代表我们从文件中读取的类就是我们实例化的那个类。
2. Externalizable 接口
Externalizable 接口是Seriazable 接口的一个子接口。这个接口中定义了两个方法:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
这个接口虽然是Serializable的子接口,但是并不是一个标记性质的接口。实现该接口的类必须强制实现这两个方法:
- wirteExternal(ObjectOutput out)
该方法中,用来控制哪些字段可以被序列化。 - readExternal(ObjectInput in)
该方法中,用来读取哪些字段可以被反序列化。
我们改造一下上面的类A
public class A implements Externalizable {
private String a;
private String b;
private int c;
public A(String a, String b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
public String toString() {
return a + ", "+ b + ", " + c;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(a);
out.writeObject(b);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.a = (String)in.readObject();
this.b = (String)in.readObject();
}
}
public static void main(String[] args) throws Exception {
A a = new A("1", "2", 3);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
out.writeObject(a);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
A a1 = (A)in.readObject();
System.out.println(a1);
}
输出结果为:
Exception in thread "main" java.io.InvalidClassException: ioPackage.A; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2041)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at ioPackage.Client.main(Client.java:14)
在相关资料中,查到:
Externalizable 接口标识的类,在进行反序列化时,会调用类的默认构造器。
由于我们自定义了A的构造器,导致默认的构造器并没有被生成,于是我们在A中添加一个默认的构造器。运行, 结果为:
1, 2, 0
因为,我们并没有对int c 进行序列化,反序列化的操作,导致c被初始化为默认值0。
是不是想要对序列化的类进行字段限制,就只能实现Externalizable
接口,实现方法呢? 并不是的,其实Serializable
接口也可以控制字段的序列化。
3.Serializable的序列化控制
transient
关键字就是用来控制Serializable
的字段的.
我们修改类A:
private String a;
transient private String b;
private int c;
public A(String a, String b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
public String toString() {
return a + ", "+ b + ", " + c;
}
运行结果为:
1, null, 3
字段String b
就被默认为null
这里需要提及的一点是:
String
类型的默认值是null
, 而不是字符串"null"
, 但是,在反序列化的过程中,被限制的String
类型的字段值是字符串"null"
.
序列化可能会遇到的问题
1. 如果我反序列化相同的类多次,它们的在java中会是同一个对象么?
由于反序列的过程就是将一个类读入内存的过程,如果多次反序列化,肯定是开辟多个内存地址.这就表示在Java中,它们是不同的两个对象.
但是,也存在例外.如果你的对象是由同一个流读进来的对象,那么它们就是相同的.
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
A a1 = (A)in.readObject();
in.close();
ObjectInputStream in2 = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
A a2 = (A)in2.readObject();
in2.close();
a1
和 a2
的地址是不同的.
A a = new A("1", "2", 3);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
out.writeObject(a);
out.writeObject(a); // 输出两次
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
A a1 = (A)in.readObject();
A a2 = (A)in.readObject();
System.out.println(a1);
System.out.println(a2);
a1
和 a2
的地址是相同的.
2. 如果序列化的类中包含其他类的对象,多次反序列化后,第三方对象会在内存中存在一份还是多份?
如果在序列化前,多个序列化类指向的对象是同一个对象,那么反序列化后指向的还是同一个对象.反序列化的结果的对象网和序列化之前是相同的.
网友评论