一、序列化、反序列化概念
序列化(Serialization)是一种将对象以一连串的字节描述的过程,将程序中的对象,放入硬盘(文件)中保存就是序列化,如果不存放在磁盘中,而是一直存放在内存中,会增大内存的消耗;序列化就是将对象的状态信息转换为可以存储或传输的形式的过程;
反序列化(Deserialization)是一种将这些字节重建成一个对象的过程,将硬盘(文件)中的字节码重新转成对象就是反序列化。
在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
- 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象)
- 在网络上传送对象的字节序列(网络传输对象) 实际上就是将数据持久化,防止一直存储在内存当中,消耗内存资源。而且序列化后也能更好的便于网络运输何传播
- 序列化:将java对象转换为字节序列
- 反序列化:把字节序列回复为原先的java对象
二、实现序列化
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常
import java.io.Serializable;
public class ZslTest implements Serializable {
private String name;
private Integer age;
private Integer score;
@Override
public String toString() {
return "ZslTest{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
测试类
import java.io.*;
public class test01 {
public static void serialize( ) throws IOException {
ZslTest ZslTest = new ZslTest();
ZslTest.setName("linko");
ZslTest.setAge( 18 );
ZslTest.setScore( 1000 );
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("ZslTest.txt") ) );
objectOutputStream.writeObject( ZslTest );
objectOutputStream.close();
System.out.println("序列化成功!已经生成ZslTest.txt文件");
System.out.println("==============================================");
}
public static void deserialize( ) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream( new FileInputStream( new File("ZslTest.txt") ) );
ZslTest ZslTest = (ZslTest) objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列化结果为:");
System.out.println( ZslTest );
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
serialize();
deserialize();
}
}
结果:
从上面可知:
JDK类库提供的序列化API
java.io.ObjectOutputStream
:表示对象输出流 它的writeObject(Object obj)
方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。java.io.ObjectInputStream
:表示对象输入流
它的readObject()
方法从源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
直接总结:
- 若对象仅仅实现了
Serializable接口
,则可以按照以下方式进行序列化和反序列化。ObjectOutputStream
采用默认的序列化方式,对象的非transient的实例变量进行序列化。ObjcetInputStream
采用默认的反序列化方式,对象的非transient的实例变量进行反序列化。- 若对象仅仅实现了
Serializable接口
,并且还定义了readObject(ObjectInputStream in)
和writeObject(ObjectOutputSteam out)
,则采用以下方式进行序列化与反序列化。ObjectOutputStream
调用对象的(重写可能存在反序列化风险)writeObject(ObjectOutputStream out)
的方法进行序列化。ObjectInputStream
会调用对象的(重写可能存在反序列化风险)readObject(ObjectInputStream in)
的方法进行反序列化。- 若对象实现了
Externalnalizable接口
,且对象必须实现readExternal(ObjectInput in)
和writeExternal(ObjectOutput out)
方法,则按照以下方式进行序列化与反序列化。ObjectOutputStream
调用对象的(重写可能存在反序列化风险)writeExternal(ObjectOutput out)
的方法进行序列化。ObjectInputStream
调用对象的(重写可能存在反序列化风险)readExternal(ObjectInput in)
的方法进行反序列化。- Serializable接口是个空接口,他是一个标记接口,只是做一个标记用!告诉代码只要是实现了Serializable接口的类都是可以被序列化的,然而真正的序列化动作不需要靠它完成。
跟踪源码到ObjectOutputStream
的writeObject0()
三、序列化的必要条件
- 必须是同包,同名。
- serialVersionUID必须一致。有时候两个类的属性稍微不一致的时候,可以通过将此属性写死值,实现序列化和反序列化。
证明serialVersionUID必须一致:
先手动给ZslTest对象加上serialVersionUID,序列化
手动删掉serialVersionUID
注释掉序列化,直接反序列化前面已经序列化的对象
总结:
- serialVersionUID是序列化前后的唯一标识符
- 默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!
- serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。
- 为了serialVersionUID的确定性,建议mplements Serializable的对象,都手动显式地为它声明一个serialVersionUID明确值!
四、静态变量序列化、父类序列化、 Transient 关键字
- 被static修饰的字段是不会被序列化的;
序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。 - 想将父类对象也序列化,就需要让父类也实现Serializable 接口;
父类没有实现 Serializable 接口时,JVM是不会序列化父对象的,但是父类不实现Serializable 接口,而他有默认的无参的构造函数,除非在父类无参构造函数中对变量进行初始化,否则父类中的变量值都是默认声明的值,如
int 型的默认是 0,string 型的默认是 null。因为:Java对象的构造必须先有父对象,才有子对象,反序列化也不例外,所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。在父类无参构造函数中对变量进行初始化,
否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
Transient
关键字的作用是控制变量的序列化,在变量声明前加上Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
五、关于RMI
RMI相关
RMI 技术是完全基于 Java 序列化技术的,服务器端接口调用所需要的参数对象来至于客户端,它们通过网络相互传输。这就涉及 RMI
的安全传输的问题。一些敏感的字段,如用户名密码(用户登录时需要对密码进行传输),我们希望对其进行加密,这时,就可以采用本节介绍的方法在客户端对密码进行加密,服务器端进行解密,确保数据传输的安全性。