1 什么是序列化和反序列化?
Java序列化是指把Java对象转换为字节序列的过程;Java反序列化是指把字节序列恢复为Java对象的过程。
2 为什么要序列化?
其实我们的对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时候就需要序列化技术。
一般Java对象的生命周期比Java虚拟机短,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库。
在多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。
Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的。
3序列化原理?
Serializable类的源码
public interface Serializable {
}
可以看到该类的内部实现完全为空,在Java IO体系中仅起一个标记的作用:
首先定义一个User对象:
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
接着我们定义一个类来读写这个User类的对象:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializableTest {
private static void write() {
User user = new User("1001", "Bob");
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\documents\\java\\leetcode\\src\\user.txt"));
outputStream.writeObject(user);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
write();
}
}
运行上述代码后,可以查看user.txt文件,其中的数据是以二进制的形式:
�� {sr java.io.NotSerializableException(Vx �5 xr java.io.ObjectStreamExceptiond��k�9�� xr java.io.IOExceptionl�sde%� xr java.lang.Exception��>;� xr java.lang.Throwable��5'9w�� L causet Ljava/lang/Throwable;L
detailMessaget Ljava/lang/String;[
此时User对象已经被持久化到文件中,接着我们将User实现Serializable接口的代码去掉:
java.io.NotSerializableException: thinking.in.java.common.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at thinking.in.java.SerializableTest.write(SerializableTest.java:13)
at thinking.in.java.SerializableTest.main(SerializableTest.java:30)
抛出了以上异常,提示不可序列化的异常,然后我们到ObjectOutputStream类中的1184行看一下,这一部分的代码是这样的:
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
如上所示,在else if中通过判断obj instanceof Serializable
,如果对象没有实现序列化接口,就无法序列化。可以想见,Java中的每一处序列化都进行了类似的检查,也就是说,没有实现Serializable接口的对象是无法通过IO操作持久化。
public class SerializableTest {
private static void read() {
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("Z:\\workspace\\practice\\user.txt"));
User user = (User) inputStream.readObject();
System.out.println(user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
read();
}
}
thinking.in.java.common.User@58372a00
此时如果将User实现Serializable接口的代码部分去掉,发现也无法将文本转换为序列化对象,反序列化异常:
java.io.InvalidClassException: thinking.in.java.common.User; class invalid for deserialization
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at thinking.in.java.SerializableTest.read(SerializableTest.java:23)
at thinking.in.java.SerializableTest.main(SerializableTest.java:30)
SerialVersionUID
对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID,如:
private static final long serialVersionUID = 1L;
在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID
与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException
异常。例如,在之前反序列化的例子中,我们故意将User类的serialVersionUID改为2L,如:
private static final long serialVersionUID = 2L;
java.io.InvalidClassException: thinking.in.java.common.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at thinking.in.java.SerializableTest.read(SerializableTest.java:23)
at thinking.in.java.SerializableTest.main(SerializableTest.java:30)
如果我们在序列化中没有显示地声明serialVersionUID
,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID
值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。