首页 > 代码库 > Java 序列化与反序列化

Java 序列化与反序列化

  • 序列化与反序列化的概念 

  把对象转换为字节序列的过程称为对象的序列化;将字节序列恢复为对象的过程称为反序列化。

  使用场景:把对象的序列保存到硬盘上,通常放在一个文件中;网络上传送对象的文件序列。

  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  • 序列化接口

  Java对象,实现了Serializable和Externalizable接口就可以实现序列化,Externalizable接口继承自Serializable接口,实现externalizable的对象完全由自身控制序列化的行为,而实现Serializable接口的对象则采用默认的序列化方式。

  java.io.ObjectOutputStream代表对象输出流,可以用writeObject(Object obj)方法将对象序列化之后,将得到的字节序列写到一个目标输出流中;

  java.io.ObjectInputStream代表对象输入流,可以用readObject()从一个源中读取序列,再将其反序列化为一个对象。

  • 序列化与反序列化步骤

  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  2) 通过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤如下:
  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
  2) 通过对象输入流的readObject()方法读取对象。

  首先,创建一个对象:

import java.io.Serializable;public class Person implements Serializable {    /**     *      */    private static final long serialVersionUID = 1L;    private int age;    private String name;    private String sex;    private int height;    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getSex() {        return sex;    }    public void setSex(String sex) {        this.sex = sex;    }    public int getHeight() {        return height;    }    public void setHeight(int height) {        this.height = height;    }    @Override    public String toString() {        return "Person [age=" + age + ", name=" + name + ", sex=" + sex + ", height=" + height + "]";    }}

  序列化与反序列化:

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;import com.changjiang.test.testFuction.entity.Person;public class TestSerializable {    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {        serialize();        deSerializePerson();    }    private static void serialize() throws FileNotFoundException, IOException {        Person p = new Person();        p.setAge(15);        p.setName("Game");        p.setHeight(178);        p.setSex("male");        ObjectOutputStream ops = new ObjectOutputStream(new FileOutputStream(new File("E:/logs/person.txt")));        ops.writeObject(p);        System.out.println("已完成对象Person序列化,并将序列化结果输出到文件");        ops.close();    }    private static void deSerializePerson() throws FileNotFoundException, IOException, ClassNotFoundException {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/logs/person.txt")));        Person p = (Person) ois.readObject();        System.out.println("已完成对象Person的反序列化,并将反序列化的结果输出到控制台:" + p);        ois.close();    }}

最后的输出结果:

已完成对象Person序列化,并将序列化结果输出到文件已完成对象Person的反序列化,并将反序列化的结果输出到控制台:Person [age=15, name=Game, sex=male, height=178]

而输出文件中的字节内容:

 sr -com.changjiang.test.testFuction.entity.Person        I ageI heightL namet Ljava/lang/String;L sexq ~ xp      瞭 Gamet male
  • serialVersionUID

  实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示:

The serializable class Person does not declare a static final serialVersionUID field of type long

编译器会自动提示以下方案来解决:

技术分享

选择前者即是用了默认的方法来生成该ID(1L),而后者则是用类名,接口名,方法和属性等来生成的一个long值。

它究竟有什么作用呢?

在之前的例子中,我们在Person类中加入了一句:

    private static final long serialVersionUID = 1L;

并且在TestSerializable这个类中将Person对象序列化进了一个文本文件,如果此时我们修改了这个ID的值,那么反序列化就会得到异常信息:

Exception in thread "main" java.io.InvalidClassException: com.changjiang.test.testFuction.entity.Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)    at com.changjiang.test.testFuction.test.TestSerializable.deSerializePerson(TestSerializable.java:34)    at com.changjiang.test.testFuction.test.TestSerializable.main(TestSerializable.java:17)

这就是说,序列化的结果是根据ID=1L计算得出的,当修改了这个值之后,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。所以,想要修改已经序列化的类之后再反序列化之前的内容,只要前后的ID一致即可。

  serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。如果不对该值显示定义出来,类的serialVersionUID的默认值则完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。所以,简单地说,希望兼容,则显示定义一致的ID,希望不兼容,则显示定义不一致的ID,为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化的类中对其赋予明确的值。

 

Java 序列化与反序列化