概念
java序列化是将对象转换成字节序列的过程;而java反序列化是将字节序列转换成对象的过程;
由来
我们都知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,比如文本、图片、音频、视频等,而这些数据都是以二进制 的形式进行发送的,那么能否实现进程间的对象传送吗?这个时候就需要对象的序列化和反序列化了:发送方需要使用序列化将对象转换成字节序列进行发送,而接收方需要使用反序列化将接收到的字节序列转换成对象; 从这里我们自然想到了序列化的好处:实现了数据的持久化 :通过序列化可以永久的将数据保存在硬盘里;实现了远程通信 :在网络上传送对象的字节序列;
JDK序列化API
java.io.ObjectOutputStream:对象输入流,通过使用其writeObject方法可以将指定的对象序列化成字节序列并保存到指定文件中
实现一:实现java.io.Serialiable接口
通过实现Serializable接口,可以开启其对象的序列化功能;可序列化的类的所有子类都是可以序列化的;下面是一个简单的可序列化的类
package org.blog.controller;
import java.io.Serializable;
/**
* @ClassName : SerialTest
* @Description : 序列化测试类
* @author Chengxi
* @Date : 2017-10-16下午7:20:50
*/
public class SerialTest implements Serializable {
private final static long serialVersionUID = 123456 L;
public int age;
private String username ;
private String password;
public void setUsername (String username){
this .username = username;
}
public String getUsername (){
return this .username;
}
public void setPassword (String password){
this .password = password;
}
public String getPassword (){
return this .password;
}
}
序列化与反序列化测试类
package org.blog.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @ClassName : SerialMain
* @Description :
* @author Chengxi
* @Date : 2017-10-16下午7:23:57
*/
public class SerialMain {
public static void writeObj (SerialTest test) throws FileNotFoundException, IOException{
File file = new File("D://serial.txt" );
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
test.setPassword("123456" );
test.setUsername("chengxi" );
test.age = 20 ;
oos.writeObject(test);
System.out.println("write ok" );
oos.close();
}
public static SerialTest readObj () throws FileNotFoundException, IOException, ClassNotFoundException{
File file = new File("D://serial.txt" );
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
SerialTest test = (SerialTest)ois.readObject();
ois.close();
return test;
}
public static void main (String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerialTest test2 = readObj();
System.out.println("username->" +test2.getUsername()+" password->" +test2.getPassword()+" age->" +test2.age);
}
}
serialVersionUID :属性格式为:private final static long serialVersionUID = xxxL;简单来说,java的序列化机制就是通过在运行时判断类的serialVersionUID的值来验证版本的一致性 的;在进行反序列化时,JVM会把传来的serialVersionUID与本地相对应的实体(类)的serialVersionUID的值进行比较,如果相等的话,就可以进行反序列化,如果不相等,就会抛出版本不一致的异常 ;进行如下测试
package org.blog.controller;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @ClassName : SerialTest
* @Description : 序列化测试类
* @author Chengxi
* @Date : 2017-10-16下午7:20:50
*/
public class SerialTest implements Serializable {
private final static long serialVersionUID = 1213456 L;
public transient int age;
private String username ;
private String password;
public void setUsername (String username){
this .username = username;
}
public String getUsername (){
return this .username;
}
public void setPassword (String password){
this .password = password;
}
public String getPassword (){
return this .password;
}
private void writeObject (ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(age);
}
private void readObject (ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
age = ois.readInt();
}
}
在我们使用测试类进行一次序列化之后,修改serialVersionUID属性的值,然后进行反序列化会抛出异常;
serialVersionUID有两种生成方式:一种是使用默认值1L,还有一种就是根据类名、接口名、成员属性和方法等来生成一个64位的哈希字段;对于该属性,如果你没有显示定义的话,Eclipse/MyEclipse等编辑器也会默认定义的;
transient修饰符 :主要用于修饰可序列化类的属性,用于自定义需要序列化的对象的属性,<font color=’red’>使用该修饰符修饰的属性不会被序列化成字节序列看如下测试代码
package org.blog.controller;
import java.io.Serializable;
/**
* @ClassName : SerialTest
* @Description : 序列化测试类
* @author Chengxi
* @Date : 2017-10-16下午7:20:50
*/
public class SerialTest implements Serializable {
private final static long serialVersionUID = 123456 L;
public transient int age;
private String username ;
private String password;
public void setUsername (String username){
this .username = username;
}
public String getUsername (){
return this .username;
}
public void setPassword (String password){
this .password = password;
}
public String getPassword (){
return this .password;
}
}
对age属性使用了transient修饰符进行修饰,进行如下序列化与反序列化测试
package org.blog.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @ClassName : SerialMain
* @Description :
* @author Chengxi
* @Date : 2017-10-16下午7:23:57
*/
public class SerialMain {
public static void writeObj (SerialTest test) throws FileNotFoundException, IOException{
File file = new File("D://serial.txt" );
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
test.setPassword("123456" );
test.setUsername("chengxi" );
test.age = 1 ;
oos.writeObject(test);
System.out.println("write ok" );
oos.close();
}
public static SerialTest readObj () throws FileNotFoundException, IOException, ClassNotFoundException{
File file = new File("D://serial.txt" );
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
SerialTest test = (SerialTest)ois.readObject();
ois.close();
return test;
}
public static void main (String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerialTest test = new SerialTest();
writeObj(test);
SerialTest test2 = readObj();
System.out.println("username->" +test2.getUsername()+" password->" +test2.getPassword()+" age->" +test2.age);
}
}
最终输出的结果为:username->chengxi password->123456 age->0,从而证实了:被transient修饰的属性不会被序列化,因此age为初始值0
重写Serializable接口的writeObject/readObject :在实现了Serializable接口的类中除了使用transient来自定义我们不想要序列化的属性外,还可以通过重写writeObject/readObject来自定义我们序列化和反序列化的方式;修改序列化类
package org.blog.controller;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @ClassName : SerialTest
* @Description : 序列化测试类
* @author Chengxi
* @Date : 2017-10-16下午7:20:50
*/
public class SerialTest implements Serializable {
private final static long serialVersionUID = 123456 L;
public transient int age;
private String username ;
private String password;
public void setUsername (String username){
this .username = username;
}
public String getUsername (){
return this .username;
}
public void setPassword (String password){
this .password = password;
}
public String getPassword (){
return this .password;
}
private void writeObject (ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(age);
}
private void readObject (ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
age = ois.readInt();
}
}
再次执行之前的测试类,会输出如下结果:
my personal writeObject
write ok
username -> chengxi password -> 123456 age -> 1
因此最终我们发现,我们使用了transient修饰符修饰了age,然而还是对age进行了序列化,因为我们自定义了我们自己的writeObject和readObject
实现二:实现Externalizable接口
该接口的定义如下:
public interface Externalizable extends Serializable {
public void writeExternal (ObjectOutput out) throws IOException ;
public void readExternal (ObjectInput in) throws IOException,
ClassNot FoundException ;
}
通过实现Externalizable接口实现序列化必须要实现writeExternal/readExternal,从而实现自定义的序列化和反序列化 测试代码如下:
package org.blog.controller;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* @ClassName : ExternalTest
* @Description :
* @author Chengxi
* @Date : 2017-10-16下午9:02:20
*/
public class ExternalTest implements Externalizable {
private String username ;
private String password ;
public String getUsername () {
return username;
}
public void setUsername (String username) {
this .username = username;
}
public String getPassword () {
return password;
}
public void setPassword (String password) {
this .password = password;
}
public void writeExternal (ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(password);
System.out.println("write object ok" );
}
public void readExternal (ObjectInput in) throws IOException,
ClassNotFoundException {
username = (String) in.readObject();
password = (String) in.readObject();
System.out.println("read object ok" );
}
}
在这个序列化类中,我们定义两个属性,username/password,并且实现了Externalizable接口中的writeExternal和readExternal方法来自定义序列化与反序列化;再来进行如下测试
/**
*
*/
package org.blog.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @ClassName : ExternalMain
* @Description :
* @author Chengxi
* @Date : 2017-10-16下午9:23:18
*
*
*/
public class ExternalMain {
public static void writeObj (ExternalTest test) throws FileNotFoundException, IOException {
File file = new File("D://serial.txt" );
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(test);
}
public static Object readObj () throws FileNotFoundException, IOException, ClassNotFoundException{
File file = new File("D://serial.txt" );
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
return ois.readObject();
}
public static void main (String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ExternalTest test1 = new ExternalTest();
test1.setPassword("cheng" );
test1.setUsername("chengxi" );
writeObj(test1);
test1 = null ;
test1= (ExternalTest) readObj();
System.out.println("username->" +test1.getUsername()+" password->" +test1.getPassword());;
}
}
最终程序输出的结果为:
write object ok
read object ok
username->chengxi password->cheng
总结
不管想要实现怎样的序列化方式,以上两种方式都是可以实现的,个人建议使用Serializable接口来实现序列化,因为Externalizable接口要求我们必须自定义序列化和反序列化的方式,而Serializable接口中,我们可以简单的使用transient修饰符即可指定不想要序列化的属性(在Externalizable接口中是没有该修饰符的),并且在实现该接口的类中我们也可以自定义序列化和反序列化的方式(通过重写readObject()和writeObject)