序列化与反序列化
一 、什么是序列化与反序列化
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化
- 一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
为什么需要实现Serilizable接口?
- 如果一个类想被序列化,需要实现Serializable接口。否则将抛出NotSerializableException异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种,这也是为什么Serializable虽然是一个空接口,但是只要实现了该接口就能序列化和反序列化。
二、为什么需要序列化与反序列化?
- 当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。当两个Java进程进行通信时,能否实现进程间的对象传送呢?如何做到呢?此时就需要Java序列化与反序列化了!发送方需要把这个Java对象转换为字节序列,然后在网络上传送。接收方需要从字节序列中恢复出Java对象。
序列化的好处
- 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)
- 用序列化实现远程通信,即在网络上传送对象的字节序列。
三、序列化特点
- 序列化时,只对对象的状态进行保存,而不管对象的方法。
- 当一个父类实现序列化时,子类自动实现序列化,不需要显示实现Serializable接口
- 当一个对象的实例变量引用了其他对象时,序列化该对象时,也把引用对象序列化(可以理解成,在这里引用对象只是当做类的属性,无特殊说明自然会把属性序列化)
- 对象中,被static或transient(对象的临时数据)修饰的变量,不会被序列化。
四、如何序列化和反序列化
利用ObjectInputStream(对象输入流)和ObjectOutputStream(对象输出流)
- 序列化:ObjectInputStream(对象输入流)
FileOutputStream fileOutputStream=new FileOutputStream(数据保存目录);
ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);//对象输入流,得到的对象序列化,把得到的字节序列写到目标输入流中
out.writeObject(序列化对象); //通过对象输入流的writeObject方法写对象
例子:Employee.java
//一个对象可序列化,需要实现Serilizable接口
public class Employee implements Serializable {
public String name;
public static String address;
public transient String password;
public int number;
public Employee(String name,String address,String password,int number)
{
this.name=name;
this.address=address;
this.password=password;
this.number=number;
}
}
序列化类 SerializeDemo.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args)
{
Employee employee=new Employee("xiao","shenqiu","12345",1);
try {
FileOutputStream fileOutputStream=new FileOutputStream("/object.txt");
ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);
try {
out.writeObject(employee);
}
catch (Exception ee)
{
ee.printStackTrace();
}
out.close();
fileOutputStream.close();
System.out.println("序列化数据已保存在文件中");
}
catch (IOException i)
{
i.printStackTrace();
}
}
}
序列化类运行结果:
序列化数据已保存在文件中
Process finished with exit code 0
用UltraEdit 打开obiect.txt文件可以看到文件内容。对象已经序列化成字节序列存储在文件中了。其中***h代表行。
- 2.反序列化
利用对象输入流ObjectInputStream
FileInputStream fileIn = new FileInputStream(数据读取目录);
ObjectInputStream in = new ObjectInputStream(fileIn);// 对象输入流
e = (Employee) in.readObject();//通过对象输入流的readObject()方法读取对象
反序列化文件 DeserializeDemo.java
import java.io.*;
public class DeserializeDemo {
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/object.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(Exception i)
{
i.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("Password: " + e.password);
System.out.println("Number: " + e.number);
}
}
反序列化运行结果:
Deserialized Employee...
Name: xiao
Address: null
Password: null
Number: 1
Process finished with exit code 0
这里可以看到Employee 的属性(static)Address和(transient)Password没有被序列化,是null。
五、Serialversion 序列化版本号
(1)提高运行效率。如果在类中没有显式声明Serialversion。那么在序列化的时候回通过计算得到该值。显式声明可以省略计算。
(2)如果类没有提供SerialversionUID,那么编译器会自动生成。SerialversionUID就是对象的hashcode。如果加入新的成员变量,那么重新生成的SerialversionUID会变化。这样反序列的时候crash,产生java.io.InvalidClassException异常,不能正常的反序列化。
例子:
我们在Employee.java中加一个属性 i,再次运行反序列化文件DeserializeDemo.java
Employee .java
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public static String address;
public transient String password;
public int number;
public int i=0;//新加属性i
public Employee(String name,String address,String password,int number)
{
this.name=name;
this.address=address;
this.password=password;
this.number=number;
}
}
运行结果:
java.io.InvalidClassException: 序列化.Employee;local class incompatible:
stream classdesc serialVersionUID = -7599142119717411520,
local class serialVersionUID = 5676476655507316830
可以看到序列化版本号不一致,导致不能反序列化,所以最好指定serialVersionUID。
我们指定serialVersionUID,看一下运行结果。
我们在Employee .java中指定serialVersionUID,运行SerializeDemo.java序列化,然后在文件Employee.java中添加属性 i,看一下反序列化的结果.
- 指定serialVersionUID。即修改Employee .java
- 序列化。即运行SerializeDemo .java
- 修改对象属性。即修改Employee.java
- 反序列化。DeserializeDemo.java
Employee .java
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public static String address;
public transient String password;
public int number;
public int i=0; //新加属性i
private static final long serialVersionUID = -8976532647273106745L;//指定serialVersionUID
public Employee(String name,String address,String password,int number)
{
this.name=name;
this.address=address;
this.password=password;
this.number=number;
}
}
运行DeserializeDemo.java文件
Deserialized Employee...
Name: xiao
Address: null
Password: null
Number: 1
可以看到此时反序列化正确。指定serialVersionUID ,那么修改序列化对象属性,反序列化时也不会出错。