一、什么是序列化和反序列化?
序列化(Serialization):将java对象以一连串的字节保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。
反序列化(deserialization):保存在磁盘文件中的java字节码重新转换成java对象称为反序列化。
二、Serializable接口:
Java类通过实现java.io.Serialization接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。可序列化类的所有子类型都是可以序列化的。序列化接口没有方法或者字段,仅用于标识可序列化的语义。
当试图对一个对象进行序列化时,如果遇到一个没有实现java.io.Serialization接口的对象时,将抛出NotSerializationException异常。
如果要序列化的类有父类,要想将在父类中定义过的变量序列化下来,那么父类也应该实现java.io.Serialization接口。
示例:
import java.io.Serializable;
public class Goods implements Serializable{
private static final long serialVersion = 123L;
private int id ;
private String name;
public Goods(){};
public Goods(String name,int id){
this.id = id;
this.name = name;
}
public String toString(){
return this.id + " " + this.name;
}
}
三、序列化:
步骤一:创建一个ObjectOutputStream输出流;
步骤二:调用ObjectOutputStream对象的writeObject输出可序列化对象。
示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main1 {
public static void main(String[] args){
//扩大作用域
FileOutputStream fos = null;
ObjectOutputStream oos = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
//对象输入流
fos = new FileOutputStream("test2");
oos = new ObjectOutputStream(fos);
Goods goods = new Goods("kekokele",0001);
//使用readObject()反序列化
oos.writeObject(goods);
//刷新输出流
oos.flush();
Goods goods1 = new Goods("xuebi",0002);
//使用readObject()反序列化
oos.writeObject(goods1);
//刷新输出流
oos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
try {
//关闭流
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行后,得到一个test2文件,文件中的内容:
四、反序列化:
步骤一:创建一个ObjectInputStream输入流;
步骤二:调用ObjectInputStream对象的readObject()得到序列化的对象。
示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main1 {
public static void main(String[] args){
//扩大作用域
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
//对象输入流
fis = new FileInputStream("test2");
ois = new ObjectInputStream(fis);
try {
//使用readObject()反序列化,注意强制类型的转换
Goods good = (Goods) ois.readObject();
//使用对象
System.out.println(good);
//使用readObject()反序列化,注意强制类型的转换
Goods good1 = (Goods) ois.readObject();
//使用对象
System.out.println(good1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
try {
//关闭流
ois.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行后,可以把序列化中输入的信息输出来:
五、序列化版本号的作用 serialVersionUID
JVM 首先会通过类名来区分 Java 类,类名不同,则不是同一个类。当类名相同时,JVM 就会通过序列化版本号来区分 Java 类,如果序列化版本号相同就为同一个类,序列化版本号不同就为不同的类。
在序列化一个对象时,如果没有指定序列化版本号,后期对该类的源码进行修改并重新编译后,会导致修改前后的序列化版本号不一致,因为 JVM 会提供一个新的序列化版本号给该类对象。
此时再用以往的反序列化代码去反序列化该类的对象,就会抛出异常 java.io.InvalidClassException ,所以序列化一个类时最好指定一个序列化版本号,或者永远不修改此类。
public class Student implements Serializable {
private static final Long serialVersionUID = 1L;
}
由 JVM 提供序列化版本号的好处是,同名却不同功能的类,会有两个不同的序列化版本号,JVM 可以通过序列化版本号加以区分,缺点是一旦修改源码,会重新提供序列化版本号,导致修改前后的序列化版本号不一致,进行反序列化时会出现运行出现异常。
由 开发人员 手动提供序列化版本号的好处是,当修改了被序列化类的源码后,以往写的反序列化代码依然可以使用,如 JDK 中的 String 类。以便后期进行增强和维护不会影响使用。
六、transient 关键字
-
这个关键字表示游离的,不参与序列化的。
-
在序列化一个对象时,如果不希望某个属性参加序列化,可以使用
transient
修饰该属性。 -
被该关键字修饰的属性不会参与到序列化中。