Java 序列化和反序列化
一、概念:
把对象转换为字节序列的过程称为对象的序列化
把字节序列恢复为对象的过程称为对象的反序列化
二、用途:
对象的序列化主要用途:
1)把对象的字节序列永久地保存到硬盘上,通常放在一个文件中
2)在网络上传送对象的字节序列
无论是何种类型的数据,都会以二进制序列化的形式在网络上传送。(远程通信)
3)在进程间传递对象
三、如何实现序列化:
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
3.1. 使用到JDK中关键类ObjectOutputStream和ObjectIntputStream
ObjectOutputStream:通过使用writeObject(Object o)方法,将对象以二进制格式进行写入
ObjectIntputStream:通过readObject(Object o)方法,从输入流中读取二进制流,转换为对象。
3.2.目标对象需要实现Serializble接口 和Externalizable接口
对象序列化包括如下步骤:
a. 创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流;
b. 通过对象输出流的writeObject()方法写对象
对象反序列化包括如下步骤:
a. 创建一个对象输入流,它可以包装一个其它类型的源输入流,如文件输入流;
b. 通过对象输入流的readObject()方法读取对象
四、栗子:
4.1实现序列化的方式一:实现Serializable接口
package test.serialize;
import java.io.Serializable;
public class PersonSerilizble1 implements Serializable {
//private static final 为了保证兼容对象的不同版本,
// 如果serialVersionUID 不显式声明,当对象被序列化(animal.txt)保存到磁盘后,新增/删减字段后,
//再对animal.txt文件反序列化时,就会因为serialVersionUID不同,即对象版本不同,不能对其反序列化;如果加了serialVersionUID,
//就能反序列化
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String sex;//加了transient 字段,表示不需要序列化
private static String kind ="Dog";//用static 修饰,说明是类变量,属于类,而不是对象,所以也不能被序列化
@Override
public String toString() {
return "PersonSerilizble1{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", kind='" + kind + '\'' +
'}';
}
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;
}
}
package serilizable;
import java.io.*;
import java.text.MessageFormat;
/**
* 2019-03-17 by wx
* 测试序列化对象和反序列化对象
*/
public class ToObjectSerializableAndDeSerializble2 {
public static void main(String[] args) throws Exception{
PersonSerializble();
PersonSerilizble1 p = PersonDeseriazable();
System.out.println(MessageFormat.format("name={0},sex={1},des{2}",p.getName()
,p.getSex(),p.getDesc()));
}
public static void PersonSerializble () throws FileNotFoundException, IOException {
PersonSerilizble1 p = new PersonSerilizble1();
p.setName("wuxia");
p.setSex("femail");
p.setDesc("dhsjdh1");
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("E:/Person.txt")));
oo.writeObject(p);
System.out.println("Person serialazble success!");
oo.close();
}
public static PersonSerilizble1 PersonDeseriazable() throws Exception,IOException{
ObjectInputStream oi = new ObjectInputStream(new FileInputStream(
new File("E:/Person.txt")));
PersonSerilizble1 p =(PersonSerilizble1) oi.readObject();
System.out.println("DeSerialazble Object success!");
return p;
}
}
运行结果如下:
文件生成位置:
序列化PersonSerilizble1对象成功后在E盘生成了一个Person.txt文件,而反序列化是读取E盘的Person.txt后生成一个PersonSerilizble1对象。
解读上面代码:
- Serializable 接口的作用只是用来标识我们这类是需要进行序列化,该接口没有提供任何方法。
- serialersionUID序列化版本号的作用是用来区分我们所编写的类的版本,用于判断反序列化时类的版本是否一致,不一致就会抛版本不一致异常。
- 如果不想某个变量进行序列化,可以使用transient关键字
注意:如果没有显式声明serialVersionUID,将对象序列化后,增加/删除字段后,反序列化就会报错,如下图,因为序列化版本id不一致,即对象的版本不一致,不被兼容。
test.serialize.ToObjectSerializableAndDeSerializble2
Exception in thread "main" java.io.InvalidClassException: test.serialize.PersonSerilizble1; local class incompatible: stream classdesc serialVersionUID = -8248111319112539511, local class serialVersionUID = 1
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
注:如果一个对象没有实现Serializble ,但又要将其序列化,会抛出一个异常:
4.2 实现序列化方式二:实现Externalizable接口
栗子:
package test.serialize;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;
/**
* 2019-09-05 序列化的实现方式二
* Externalizable 其实是继承了serializable 接口,但是额外增了了2个方法,方便扩展,也可以自定义序列化顺序
*/
public class Animal implements Externalizable {
private String name;
private String color;
private transient int age;//加了transient 字段,表示不需要序列化
private Double friends;
public Double getFriends() {
return friends;
}
public void setFriends(Double friends) {
this.friends = friends;
}
public Animal(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public Animal() {
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", friends='" + friends + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void yeild(String name){
System.out.println(name+"yeild..........");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
Date d = new Date();//自定义序列化顺序
out.writeObject(d);
out.writeObject(name);
out.writeObject(color);
out.writeObject(age);
out.writeObject(friends);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
Date d = (Date) in.readObject();//顺序是按照writeObject的顺序,
Animal a = new Animal();
a.setName((String) in.readObject());
a.setColor((String) in.readObject());
a.setAge((int) in.readObject());
a.setFriends((Double) in.readObject());
System.out.println("date="+d +"animal:"+a.toString()+" ");
}
}
----------------------------------------------
package test.serialize;
import java.io.*;
public class ExternalizableSerializeTest {
public static void main(String[] args)throws Exception, IOException,EOFException {
Animal a = new Animal("dog","white",3);
a.setFriends(44.0);
//序列化
OutputStream os = new FileOutputStream("E:/animal.txt");
ObjectOutput oo = new ObjectOutputStream(os);
a.writeExternal(oo);//序列化乱码 因为是二进制字节序列
System.out.println("Externalizable is successfully!!!" );
//反序列化
Animal a1 = new Animal();
ObjectInput oi = new ObjectInputStream(new FileInputStream("E:/animal.txt"));
a1.readExternal(oi);
System.out.println("DEExternalizable is successfully!!!");
}
}
五、serialVersionUID的作用:
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码做了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
还可以理解为:当前类型的唯一标示,每个对象在序列化的时候都会写入外部类型的这个版本号,反序列化的时候首先会检查二进制文件中的版本号与目标类型的版本号是否一样,不一样将拒绝反序列化。
显示定义serialVersionUID有2个用途:
- 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
- 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
六、思维导图: