序列化:将对象数据转化为方便磁盘存储或网络传输的格式,即将对象转为二进制形式。
反序列化:把磁盘存储或者网络传输的数据转化为对象的形式,即将二进制转化乘对象。
使用场景:远程方法调用(remote method invoke)
序列化算法的一般步骤:
(1)将对象实例相关的类元数据输出。
(2)递归地输出类的超类描述直到不再有超类。
(3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
(4)从上至下递归输出实例的数据
Serialization是个标记接口,不包括任何方法,与ObjectOutputStream输出流和ObjectInputStream输入流联用
//Person.java
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + " " + age;
}
}
//Transfer.java
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class Transfer {
public static void main(String[] args) {
Person p = new Person("a", 13);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("DataSet_1/1.txt"));
out.writeObject(p);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
//Person.java
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public Person(){} //反序列化不使用构造方法
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + " " + age;
}
}
//Recover.java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class Recover {
public static void main(String[] args) {
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream("DataSet_1/1.txt"));
Person p = (Person)input.readObject();
System.out.println(p);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
//a 13
如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。
同一对象多次序列化,只会序列化这个对象一次
private static final long serialVersionUID = 1L;
序列化ID决定着是否能够成功反序列化。java的序列化机制是通过判断运行时类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
自定义序列化ID的情况:
- 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
- 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
- 如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。
Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。
static,transient修饰的变量不能被序列化
另一种序列化的方式是实现Externalizable接口,强制自定义序列化,需要实现writeExternal、readExternal方法
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class Student implements Externalizable{
private String name;
private int age;
//必须有无参构造方法
public Student(){}
public Student(String name, int age){
this.name = name;
this.age = age;
}
public String toString() {
return name + " " + age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
this.name = (String)in.readObject();
this.age = in.readInt();
}
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("DataSet_1/Student.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("DataSet_1/Student.txt"));
out.writeObject(new Student("bob", 11));
Student s = (Student)in.readObject();
System.out.println(s);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
Externalizable接口不同于Serializable接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供pulic的无参构造器,因为在反序列化的时候需要反射创建对象。
其他序列化框架
参考:
https://zhuanlan.zhihu.com/p/449460157
https://blog.csdn.net/qq_35890572/article/details/81630052