java序列化与反序列化

来源:互联网 发布:mac删除virtualbox 编辑:程序博客网 时间:2024/06/16 17:45

概念

java序列化是将对象转换成字节序列的过程;而java反序列化是将字节序列转换成对象的过程;


由来

我们都知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,比如文本、图片、音频、视频等,而这些数据都是以二进制的形式进行发送的,那么能否实现进程间的对象传送吗?这个时候就需要对象的序列化和反序列化了:发送方需要使用序列化将对象转换成字节序列进行发送,而接收方需要使用反序列化将接收到的字节序列转换成对象;从这里我们自然想到了序列化的好处:实现了数据的持久化:通过序列化可以永久的将数据保存在硬盘里;实现了远程通信:在网络上传送对象的字节序列;


JDK序列化API

java.io.ObjectInputStream:对象输出流,通过使用其readObject方法可以将原文件中的字节序列反序列成为一个对象并返回
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 = 123456L;    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 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->20; 对于实现了Serializable接口的类,可以使用ObjectOutputStream对象输出流将其对象序列化writeObject保存到文件中,使用ObjectInputStream对象输入流将指定文件的字节序列反序列化然后readObject读取并返回;
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 = 1213456L;    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;    }    //@Override 序列化方式    private void writeObject(ObjectOutputStream oos) throws IOException {        //执行默认的序列化方法        oos.defaultWriteObject();        oos.writeInt(age);    }    //@Override 反序列化方式    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 = 123456L;    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 = 123456L;    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;    }    //@Override 序列化方式    private void writeObject(ObjectOutputStream oos) throws IOException {        //执行默认的序列化方法        oos.defaultWriteObject();        oos.writeInt(age);    }    //@Override 反序列化方式    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {        ois.defaultReadObject();        age = ois.readInt();    }}
再次执行之前的测试类,会输出如下结果:
my personal writeObjectwrite okusername->chengxi password->123456 age->1
因此最终我们发现,我们使用了transient修饰符修饰了age,然而还是对age进行了序列化,因为我们自定义了我们自己的writeObject和readObject
Serializable接口总结:通过使用Serializable接口,可以实现对象的序列化。如果仅仅实现了Serilizable接口,那么就可以使用ObjectOutputStream进行默认序列化将没有transient修饰符的属性进行序列化,然后通过ObjectInputStream进行默认反序列化来返回对应的对象;如果还重写了该接口的writeObject(ObjectOutputStream oos)和readObject(ObjectInptutStream ois),那么ObjectOutputStream和ObjectInputStream就会使用自定义的序列化和反序列化方式进行操作;对于transient和readObject/writeObject两者,个人归纳为writeObejct/readObject强于transient


实现二:实现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 okread object okusername->chengxi password->cheng
总结: 通过实现Externalizable接口,我们也可以实现序列化,通过实现也必须实现该接口的writeExternal()和readExternal()方法来自定义序列化和反序列化的方式;最终在测试类中,使用ObjectOutputStream实质是通过调用序列化对象的writeExternal()方法来实现序列化的,使用ObjectInputStream实质是通过调用序列化对象的readExternal()方法来实现反序列化的


总结

不管想要实现怎样的序列化方式,以上两种方式都是可以实现的,个人建议使用Serializable接口来实现序列化,因为Externalizable接口要求我们必须自定义序列化和反序列化的方式,而Serializable接口中,我们可以简单的使用transient修饰符即可指定不想要序列化的属性(在Externalizable接口中是没有该修饰符的),并且在实现该接口的类中我们也可以自定义序列化和反序列化的方式(通过重写readObject()和writeObject)
原创粉丝点击