Java 序列化与反序列化

Posted 幽幽子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 序列化与反序列化的主要内容,如果未能解决你的问题,请参考以下文章

Java序列化与反序列化

Java反序列化漏洞——反射与反序列化基础

Java序列化与反序列化

Java序列化与反序列化

Java序列化与反序列化

Java序列化与反序列化