序列化和反序列化
1、定义:
百度百科上的定义:是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
2、初步理解
序列化和反序列化:就是将一个可序列化的Object对象用一种系统才看的懂的方式在指定文件中写入和读出。计算机使用的这种方式我们看不懂,我们也没有必要看懂,我们只需要知道它写入和读出的内容而已。就好像是我们在翻译官的陪同下和一个外国人聊天,我们不需要知道那个外国人说了什么,我们只需要知道翻译官翻译出来的话就可以了。
(1)要使对象变得可序列化,只需要实现Serializable接口即可,在实现Serializable后,无需实现任何的抽象方法。
import java.io.Serializable;
public class Admin implements Serializable {
private String ID;
private String password;
public Admin() {
}
public Admin(String ID, String password) {
this.ID = ID;
this.password = password;
}
@Override
public String toString() {
return "Admin{" +
"ID='" + ID + '\'' +
", password='" + password + '\'' +
'}';
}
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
(2)序列化和可序列化:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Admin admin1 = new Admin("123","123");
//序列化
/*
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("admin.txt"));
拆分出来就是下面的两行
*/
FileOutputStream fos = new FileOutputStream("admin.txt");//创建文件输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);//创建一个Object的输出流
oos.writeObject(admin1);//将admin1写入admin。txt中
oos.close();
//反序列化
/*
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("admin.txt"));
拆分出来就是下面的两行
*/
FileInputStream fis = new FileInputStream("admin.txt");//创建文件输入流
ObjectInputStream ois = new ObjectInputStream(fis);//创建一个Object的输入流
Admin admin2 = (Admin) ois.readObject();//将admin.txt中的内容反序列出来,并强转成Admin类型
System.out.println(admin2.toString());
}
}
序列化结果:我们可以看到admin1对象写入文件后的结果是一堆的乱码,我们根本就看不懂
反序列化结果:但是反序列化之后,我们可以看到反序列化所读出的数据和序列化前写入的数据是一样的。
3、serialVersionUID
在之前的序列化和反序列化中,其实需要满足一个条件,那就是Admin类没有过改动,一旦Admin类有所改动,反序列化就会失败,也就是无法读出数据。
如果,在序列化后修改了Admin类的内容(如增加name属性),然后再反序列化,则会报一下错误:
从上面的报错我们可以看到,此次报错是由于serialVersionUID不一致二导致的,
那么serialVersionUID是啥,为啥它就忽然蹦出来了,为啥它就忽然之间就不一致了呢?
(1)原因:
serialVersionUID是计算机在进行序列化时用于标识写入和读取的内容的,和相当于是可序列化类的“身份证”,计算机在序列化(写入数据)时,如果我们没有给这个数据的类一个“身份证”(也就是serialVersionUID),那么计算机就会自动生成一个“身份证”;在反序列化(读出数据)时,计算机会根据数据的类的“身份证”来读取这个数据,如果这个“身份证”不一致,那么反序列化就会失败。而对类的修改恰好就会改变这个自动生成的“身份证”的数值。
(2)解决办法:
其实解决办法很简单,既然计算机自动生成的“身份证”可以会改变,那我们就自己定义一个“身份证”,并且让这个“身份证”这辈子都不能改变并且和这个类绑定在一起就可以,这个“身份证”就是serialVersionUID,而要使其一直不变,只需要将其定义成静态常量就可以了。所以只要在为类实现可序列化接口时,多定义一句话就可以了。
private static final long serialVersionUID = 1L;
(3)测试:
在定义Admin类时,我们只多定义一个静态常量serialVersionUID,这时其运行结果为:
在为Admin类添加属性name后,其反序列化不报错,name显示为空是因为我们存储时就没有为name赋值。
4、改进
我们可以不将异常抛出,而是直接使用try-catch来直接处理异常,由于我们的各种流都是在写入或读出之后就要关闭,所以我们可以将其和try-with-resources搭配使用。
(1)将序列化的代码修改并包装在一个方法内:
/**
* 序列化
* @param admin
*/
public static void serialize(Admin admin){
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("admin.txt"))){
oos.writeObject(admin);
oos.flush();
} catch (FileNotFoundException e) {
System.out.println("找不到文件异常的处理");
} catch (IOException e) {
System.out.println("IO异常的处理");
}
}
(1)将反序列化的代码修改并包装在一个方法内:
/**
* 反序列化
* @return
*/
public static Admin deserialize(){
Admin admin = new Admin();
try( ObjectInputStream ois = new ObjectInputStream(new FileInputStream("admin.txt"))) {
admin = (Admin) ois.readObject();
} catch (FileNotFoundException e) {
System.out.println("找不到文件异常的处理");
} catch (IOException e) {
System.out.println("IO异常的处理");
} catch (ClassNotFoundException e) {
System.out.println("找不到类的异常的处理");
}
return admin;
}
(3)调用序列化和反序列化的main方法
public static void main(String[] args) {
Admin admin1 = new Admin("123","123","黄一");
//序列化
serialize(admin1);
//反序列化
Admin admin2 = deserialize();
System.out.println("写入的数据"+admin1.toString());
System.out.println("读出的数据"+admin2.toString());
}
(4)测试结果: