一、概叙
我们知道在两个进程进行远程通信的时候,可以相互发送各种数据,包括文本、图片、音频和视频等,而这些内容在网络间都是通过二进制序列的形式进行传输的。那么如果我们要在网络间传输对象需要做些什么呢?当然这就是我们下面提到的序列化和反序列化。
针对发送方,需要将对象转换成字节序列,然后在网络上进行传输,针对接收方将从网络上接受来的字节序列转换成对象。
那么序列化的好处是什么呢?
(1)使用序列化可以实现将对象进行永久性的保存,即保存到本地文档或者数据库。
(2)使用序列化可以实现对象在进程间进行传输。
(3)使用序列化可以带到在网络间传递对象的目的。
二、定义
序列化:将java对象转换成字节序列的过程,主要是为了方便对象状态的保存和重建。
反序列化:将字节序列转换成java对象的过程。
在这里需要注意的一点是在对对象进行序列化的时候,不仅仅要保存对象的类信息而且还要对超类信息进行保存。
三、Java如何实现序列化和反序列化
1、JDK类库中序列化和反序列化API
(1)java.io.ObjectOutputStream:表示对象输出流;
它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
(2)java.io.ObjectInputStream:表示对象输入流;
它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;
2、实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
3、实现Java对象序列化与反序列化的方法
假定一个User类,它的对象需要序列化,可以有如下三种方法:
(1)若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化
ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化。
ObjcetInputStream采用默认的反序列化方式,对对User对象的非transient的实例变量进行反序列化。
(2)若User类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。
(3)若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。
4、序列化和反序列化的示例
为了更好地理解Java序列化与反序列化,举一个简单的示例如下:
package com.liutao.serializable;
import com.liutao.IO.Person;
import java.io.*;
/**
* @Author:LIUTAO
* @Description:
* @Date:Created in 17:18 2018/4/22
* @Modified By:
*/
public class JDKDemo {
static String file = "D:\\IdeaProjects\\serialable.txt";
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("liu","tao");
//进行对象的序列化
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
//进行字节流的反序列化
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Person person1 = (Person) objectInputStream.readObject();
System.out.println(person1);
}
}
我们打开seriable.txt文件就可以看见person对象的字节序列:
类似于我们Java代码编译之后的.class文件,每一个字符都代表一定的含义。序列化和反序列化的过程就是生成和解析上述字符的过程!
序列化图示:
反序列化图示:
四、注意事项
1、序列化时,只对对象的状态进行保存,而不管对象的方法;
2、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
3、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
4、并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
5、声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
6、序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途:
在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
7、Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是比如HashTable就没有实现serializable接口;
8、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;
五、总结
目前我们进行序列化的时候不仅仅可以使用JDK提供的序列化工具,而且可以使用Hessian、ProtoBuff进行序列化。