详解java序列化和反序列化
前言
当我们去看序列化时,决不能只停留在概念层次,得从源码角度去进行剖析,对于java序列化,键入就可以看到相应的源码。
相关的有有五项内容:
java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.ObjectOutput
java.io.ObjectInput
java.io.Externalizable
这些内容是什么呢?我们来看一下下面的解析就能够有所收获:
假定一个User类,它的对象需要序列化,可以有如下三种方法:
若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化。ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化。ObjcetInputStream采用默认的反序列化方式,对对User对象的非transient的实例变量进行反序列化。
若User类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in) 和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。
若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。
java.io.ObjectOutputStream:对象输出流,它的writeObject(Object obj)方法可以对指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream:对象输入流,它的readObject()方法可以将从输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
这样看我们就很好理解了,上面五个都是和序列化相关的接口和类。
一、什么是序列化和反序列化?
还是回归到最根本的环节,序列化和反序列化是什么样的概念?序列化是指将Java对象转换为字节序列的过程,而反序列化则是将字节序列转换为Java对象的过程。Java对象序列化是将实现了Serializable接口的对象转换成一个字节序列,能够通过网络传输、文件存储等方式传输 ,传输过程中却不必担心数据在不同机器、不同环境下发生改变,也不必关心字节的顺序或其他任何细节,并能够在以后将这个字节序列完全恢复为原来的对象(恢复这一过程称之为反序列化)。 对象的序列化是非常有趣的,因为利用它可以实现轻量级持久性,“持久性”意味着一个对象的生存周期不单单取决于程序是否正在运行,它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,从而达到实现对象的持久性的效果。本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。
二、序列化和反序列化示例
1.实体类实现序列化接口
public class User implements Serializable {
private String username;
private String password;
……
}
2.具体实现案例
public class SerialDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化对象User
FileOutputStream fos = new FileOutputStream("object.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user1 = new User("xcbeyond", "123456789");
oos.writeObject(user1);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user2 = (User) ois.readObject();
System.out.println(user2.getUsername()+ "," + user2.getPassword());
}
}
总结
如果声明为static和transient类型的成员变量不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
还有就是序列化运行时会使用一个称为 serialVersionUID 的版本号,并与每个可序列化的类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 “serialVersionUID” 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID。如果序列化的类未显式的声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java™ 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID,原因是这种声明仅应用于直接声明类 – serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的。那么实现的我们就不需要对其进行序列化,没有实现的就需要实现序列化了。