一、序列化简介?
在项目中有很多情况需要对实例对象进行序列化与反序列化,这样可以持久的保存对象的状态,甚至在各个组件之间进行对象传递和远程调用。序列化机制是项目中必不可少的常用机制。
序列化: 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存的java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
反序列化: 客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
总结:
- java序列化是指把java对象转换为字节序列的过程,而java反序列化是指把字节序列恢复为java对象的过程
- 序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。
二、为什么要序列化与反序列化?
当两个进程进行远程通信时,可以相互发送各种类型的数据,当两个java进行进行通信时,要传送对象,怎么传对象,通过序列化与反序列化。也就是说,发送方需要把对象转换为字节序列,然后在网络上传送,另一方面,接收方需要从字节序列中恢复出java对象。
序列化的好处:
- 永久性保存对象,保存对象的字节序列到本地文件或者数据库中,实现了数据的持久化,通过序列化可以把数据永久的保存到硬盘上,
- 利用序列化实现远程通信,可以在网络上传送对象的字节序列。
- 在进程间传递对象
三、 序列化的使用
Java 序列化方法可以分为两种:
1.实现 Serializable 接口:可以自定义
writeObject、readObject、writeReplace、readResolve 方法,会通过反射调用。
2. 实现 Externalizable 接口:需要实现 writeExternal 和 readExternal 方法。
实现Serializable 接口
Java 的序列化使用起来很简单,实现 Serializable 接口即可,实际由 ObjectOutputStream、ObjectInputStream 完成对这个标记接口的处理。
public class Student implements Serializable {
private static final long serialVersionUID = 4234796420521675233L;
private String name;
private int age;
private String sex;
private String phone;
public static long getSerialVersionUID() {
return serialVersionUID;
}
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;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
//Student 测试类
public class SerializableMain {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(30);
student.setSex("男");
student.setPhone("10086");
serialize(student);
Student s = (Student) deSerilize();
System.out.println("姓名:" + s.getName()+"\n年龄:"+ s.getAge()+"\n性别:"+s.getSex()+"\n手机:"+s.getPhone());
}
//序列化
public static void serialize(Student student){
OutputStream outStream = null;
ObjectOutputStream objectOutStream = null;
File file = new File("D:/student.out");
try {
outStream = new FileOutputStream(file);
objectOutStream = new ObjectOutputStream(outStream);
objectOutStream.writeObject(student);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (objectOutStream != null){
try {
objectOutStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outStream != null){
try {
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//反序列化
public static Object deSerilize(){
Student student = new Student();
InputStream inputStream = null;
ObjectInputStream objectInputStream = null;
File f = new File("D:/student.out");
try {
inputStream = new FileInputStream(f);
objectInputStream = new ObjectInputStream(inputStream);
student = (Student)objectInputStream.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(objectInputStream != null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return student;
}
}
结果:
writeObject方法,readObject方法的使用
writeObject()是在ObjectOutputStream中定义的方法,使用这个方法可以将目标对象写入到流中,从而实现对象序列化。但是Java为我们提供了自定义writeObject()方法的功能,当我们在目标类中自定义writeObject()方法之后,将会首先调用我们自定义的方法,然后在继续执行原有的方法步骤(使用defaultWriteObject方法)。
该方法是与writeObject方法相对应的,是用于读取序列化内容的方法,其他功能与writeObject()类似。
private void writeObject(ObjectOutputStream objectOutStream) throws IOException {
phone = phone+"-"+name;
objectOutStream.defaultWriteObject();
}
假如只自定义了writeObject()方法。
即定义了writeObject()方法,又定义了readObject()方法:
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
objectInputStream.defaultReadObject();
phone = phone.split("-")[0];
}
四、常见问题
- 序列化 ID 的问题
两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 User 序列化为二进制数据再传给 B,B 反序列化得到 User。假如在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
原因:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致。Code-2 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
private static final long serialVersionUID = 4234796420521675233L;
- 静态字段不会序列化
序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
- transient关键字的使用
transient 关键字有两个特性:
- 凡是被该关键字修饰的字段,都将被序列化过滤掉,即不会被序列化。
- transient 修饰过的成员反序列化后将赋予默认值,即 0 或 null。
自定义序列化接口:Externalizable
不像 Serializable 接口只是一个标记接口,里面的接口方法都是可选的(可实现可不实现,如果不实现则启用其自动序列化功能),而 Externalizable 接口不是一个标记接口,它强制你自己动手实现串行化和反串行化算法。
public interface Externalizable extends java.io.Serializable {
// 串行化
void writeExternal(ObjectOutput out) throws IOException;
// 反串行化
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
总结
1、如果类a仅仅实现了Serializable接口:
- ObjectOutputStream采用默认的序列化方式,对a对象的非transient实例变量进行序列化
- ObjectInputStream采用默认的反序列化方式,对a对象的非transient实例变量进行反序列化
2、如果类a仅仅实现了Serializable接口,并且还定义了a对象的writeObject(ObjectOutputStream out) 和readObject(ObjectInputStream in):
-
ObjectOutputStream调用a对象的writeObject(ObjectOutputStream out)的方法进行序列化
-
ObjectInputStream调用a对象的readObject(ObjectInputStream in)的方法进行序列化
3、如果a类实现了ExternaInalizable接口,且User类必须实现readExternam(ObjectInput in)和wiriteExternal(ObjectOutput out)方法:
-
ObjectOutputStream调用a对象的wiriteExternal(ObjectOutput out)的方法进行序列化
-
ObjectInputStream调用a对象的readExternam(ObjectInput in)的方法进行序列化