首先什么是序列化和反序列化?
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
通俗的将就是在代码运行的时候,我们可以看到很多的对象,可以是一个,也可以是一类对象的集合,很多的对象数据,这些数据中,有些信息我们想让他持久的保存起来,那么这个序列化。就是把内存里面的这些对象给变成一连串的字节描述的过程。常见的就是变成文件
其次什么场景会用到序列化与反序列化呢?
主要用于存储对象状态为另一种通用格式,比如存储为二进制、xml、json等等,把对象转换成这种格式就叫序列化,而反序列化通常是从这种格式转换回来。使用序列化主要是因为跨平台和对象存储的需求,因为网络上只允许字符串或者二进制格式,而文件需要使用二进制流格式,如果想把一个内存中的对象存储下来就必须使用序列化转换为xml(字符串)、json(字符串)或二进制(流)
再次如何实现序列化和反序列化?
实现Serializable接口
具体代码实现如下:
/**
*实体类对象
*/
public class Person implements Serializable {
/** serialVersionUID*/
private static final long serialVersionUID = 123456789L;
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age +
", sex=" + sex +
"]";
}
}
/**
*序列化与反序列化的测试demo
*/
public class Test {
public static void main(String[] args) throws Exception {
SerializableTest();
Person person=getPersonMsg();
System.out.println(person.toString());
}
//序列化
public static void SerializableTest() throws Exception{
Person ps=new Person();
ps.setAge(12);
ps.setName("张三");
ObjectOutputStream ops=new ObjectOutputStream(new FileOutputStream(new File("D:/person.txt")));
ops.writeObject(ps);
System.out.println("序列化完成");
ops.close();
}
//反序列化
public static Person getPersonMsg() throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(new File("D:/person.txt")));
Person person=(Person) ois.readObject();
System.out.println("反序列化完成");
ois.close();
return person;
}
}
对上面的2个操作文件流的类的简单说明
ObjectOutputStream代表对象输出流:
它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream代表对象输入流:
它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
具体看运行情况,输出的结果为:
我们实现了Person对象的序列化与反序列化。
static和transient 修饰的属性不能被实例化
接下来我们进行实例测试下给Person对象添加属性no和hobby
public class Person implements Serializable {
/** serialVersionUID*/
private static final long serialVersionUID = 123456789L;
private String name;
private int age;
private String sex;
private static String hobby="打游戏";
transient private String no;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age +
", sex=" + sex +
", hobby=" + hobby +
", no=" + no +
"]";
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
}
public class Test {
public static void main(String[] args) throws Exception {
SerializableTest();
Person person=getPersonMsg();
System.out.println(person.toString());
}
//序列化
public static void SerializableTest() throws Exception{
Person ps=new Person();
ps.setAge(12);
ps.setName("张三");
ps.setNo("123456");
ObjectOutputStream ops=new ObjectOutputStream(new FileOutputStream(new File("D:/person.txt")));
ops.writeObject(ps);
System.out.println("序列化完成");
ops.close();
}
//反序列化
public static Person getPersonMsg() throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(new File("D:/person.txt")));
Person person=(Person) ois.readObject();
System.out.println("反序列化完成");
ois.close();
return person;
}
}
运行main方法查看结果:
然后我们修改hobby="跑步",注释掉Test里的序列化方法,直接反序列化Person对象,查看结果:
通过这次测试,得出结论,这个静态static的属性,和transient,他们不序列化。
SerialVersionUID 的作用和用法
1.直接把model的类实现的这个接口去掉。然后执行后面的序列化和反序列化的方法。直接报错。
抛异常:NotSerializableException
2.先删除UID,执行序列化方法,然后给Person对象添加address属性,再执行反序列化方法
抛异常:InvalidClassException
因为我对象里面是没有明确的给这个 serialVersionUID 赋值,但是,Java会自动的给我赋值的,这个值跟这个对象的属性相关计算出来的。我保存的时候,也就是我序列化的时候,那时候还没有这个addTip属性呢,所以,自动生成的serialVersionUID 这个值,在我反序列化的时候Java自动生成的这个serialVersionUID值是不同的,他就抛异常啦
我们在serialVersionUID赋值后在序列化,在加属性反序列化就不会抛异常了
下面解释这个 serialVersionUID 的值到底怎么设置才OK。
首先,你可以不用自己去赋值,Java会给你赋值,但是,这个就会出现上面的bug,很不安全,所以,还得自己手动的来。
那么,我该怎么赋值,eclipse可能会自动给你赋值个一长串数字。这个是没必要的。
可以简单的赋值个 1L,这就可以啦。。这样可以确保代码一致时反序列化成功。
不同的serialVersionUID的值,会影响到反序列化,也就是数据的读取,你写1L,注意L大些。计算机是不区分大小写的,但是,作为观众的我们,是要区分1和L的l,所以说,这个值,闲的没事不要乱动,不然一个版本升级,旧数据就不兼容了,你还不知道问题在哪。。。
下面是摘自 jdk api 文档里面关于接口 Serializable 的描述
类通过实现 java.io.Serializable 接口以启用其序列化功能。
未实现此接口的类将无法使其任何状态序列化或反序列化。
可序列化类的所有子类型本身都是可序列化的。因为实现接口也是间接的等同于继承。
序列化接口没有方法或字段,仅用于标识可序列化的语义。
关于 serialVersionUID 的描述
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。