首先,我们先说一下什么是对象流的序列化与反序列化。我们知道代码创建的对象起初是存在计算机内存中的,将内存中的数据存入磁盘则是“序列化”;将磁盘中的文件数据重新加载到内存,称为“返序列化”;将内存中的数据先封装成对象,再将对象与流的形式进行与硬件磁盘,内存的交互行为,则称之为“对象流的序列化与反序列化”。
java针对对象流的序列化与反序列化提供了专门的类来处理,这个类是:ObjectInputStream(输入流)和ObjectOutputStream(输出流)
废物我们不多说,直接上代码:
package com.wenxue.io;
import java.io.*;
/**
* @className: ObjectStreamSerialTest
* @Description: TODO 对象流的序列化与反序列化
* @version: v1.8.0
* @author: GONGWENXUE 《微信公众号:Java深度编程》
* @date: 2020/6/13 16:28
*/
public class ObjectStreamSerialTest {
public static void main(String[] args) {
wirterObject();
readObject();
}
/**
* @Author GONGWENXUE
* @Description //TODO 写出对象,序列号对象
* @version: v1.8.0
* @Date 16:37 2020/6/13
* @Param
* @return
**/
public static void wirterObject() {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("C:\\user.bat"));
User user = new User(17, "文学");
out.writeObject(user);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @Author GONGWENXUE
* @Description //TODO 读对象,反序列号对象
* @version: v1.8.0
* @Date 16:37 2020/6/13
* @Param
* @return
**/
public static void readObject() {
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("C:\\user.bat"));
User user = (User)in.readObject();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
static class User implements Serializable{
int age;
String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
}
}
当运行wirterObject()方法时,会在C盘下生产一个文件,如下:
使用Notepad++打开此user.bat文件,显示内容如下:
¬í sr )com.wenxue.io.ObjectStreamSerialTest$User¹0ì]|J|Õ I ageL namet Ljava/lang/String;xp t æ–‡å¦
当调用readObject()时,可以重新将序列化的user对象再次的°读回内存,但这并不一定会成功,这是为什么呢?
试想一下,我们有这么一个应用场景,在反序列化的时候User类对象可能已经经过了多次的修改,版本已经升级过多次了,可能已经和当初序列化时的数据结构,类型,方法等均不一致了,从而导致无法正常进行反序列化。要解决这个问题就需要使用到java的版本管理机制。
java为了解决此类问题,特意推出了SerialVersionUID来解决这个问题。该值常出现在类的成员变量中,鲜少有人注意,大部人也不知道是用来干嘛用的。
public class ObjectStreamSerialTest {
public static final long serialVersionUID = -34933310492167L;
//……
}
这个值是怎么来的,首先我们需要了解下对象序列化的文件格式,及SHA算法。
下对象序列化的文件格式
对象序列化是以特殊的文件格式存储对象的,当存储一个对象的时候也必须要存储这个类,其中包含了:
指纹是通过对类,超类,接口,域类型和方法签名按照规范方法排序,然后将安全散列算法(SHA)应用与这个数据而获得的。
SHA是一种可以为较大信息会提供指纹的快速算法,这种指纹总是20个字节的数据包。java对象的序列化机制采取了SHA码的前8个字节作为类的指纹。在读入一个对象的时候,会拿着指纹与当前类的指纹比对,如果不匹配,说明这个类已经产生了变化,因此反序列化时会产生异常。而使用SerialVersionUID后就指定了类的指纹一定就是这个了,所以反序列化的时候能够匹配上,但这也不代表就一定能反序列化成功,这又是为何呢?
当类被修改以后,假如只是修改了方法是不会影响反序列化的,但如果是变量就不一定了。我们假设有这么一个场景,User类在修改前有一个属性,int a = 1; 修改后User类的属性变成了String a;那么这时候java的反序列化机制会自动匹配类型,匹配不成功就会自动转换其类型(将int装成String,得到String a = "1"),此种情况自然能够转换成功。但假如User类修改前是String s = “abc”, 修改后是 int a; 那么怎么可能将字符串“abc”转成int类型呢,所以一定会失败。所以使用SerialVersionUID也未必能保证序列化后一定能反序列化成功。