美文网首页
序列化与反序列化

序列化与反序列化

作者: 殷俊杰 | 来源:发表于2018-08-31 21:53 被阅读0次

Java基础学习总结——Java对象的序列化和反序列化

一、序列化和反序列化的概念

把对象转换为字节序列的过程称为对象的序列化
  把字节序列恢复为对象的过程称为对象的反序列化
  对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

二、JDK类库中的序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
  java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  2) 通过对象输出流的writeObject()方法写对象。

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

三、对象序列化和反序列范例

package com.yjj.serialize.object;

import java.io.Serializable;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:08
 * @Version: 1.0
 */
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer age;
    private String name;

    public Person() {
    }

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

序列化

package com.yjj.serialize.serialize;

import com.yjj.serialize.object.Person;

import java.io.*;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:06
 * @Version: 1.0
 */
public class SerializeTest {
    public static void main(String[] args) {
        Person person = new Person(12, "殷俊杰");
        try {
            serialize(person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void serialize(Object object) throws IOException {
        Class clazz = object.getClass();
        System.out.println(clazz.getName());
        File file = new File("E:/a.txt");
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(object);
            System.out.printf("序列化对象%s成功",clazz.getName());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (objectOutputStream != null) {
                objectOutputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        }
    }
}

输出如下

com.yjj.serialize.object.Person
序列化对象com.yjj.serialize.object.Person成功
Process finished with exit code 0

反序列化

package com.yjj.serialize.deserialize;

import com.yjj.serialize.object.Person;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:45
 * @Version: 1.0
 */
public class DeserializeTest {
    public static void main(String[] args) {
        File file=new File("E:/a.txt");
        Object object=deserialize(file);
        Person person= (Person) object;
        System.out.println(person);
    }

    public static Object deserialize(File file){
        try {
            ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
            Object object=objectInputStream.readObject();
            return object;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

输出如下

Person{age=12, name='殷俊杰'}

serialVersionUID的作用

在序列化时显示指定serialVersionUID作为版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量,如果不显示指定,jvm会隐式给我们随机生成一个serialVersionUID,这个serialVersionUID是根据对象的信息生成的,如果我们对序列化对象有所改动的话,serialVersionUID也会随之改动,再反序列化就会因serialVersionUID不一致而报错、
示例:

package com.yjj.serialize.object;

import java.io.Serializable;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:36
 * @Version: 1.0
 */
public class User implements Serializable{
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

序列化和反序列化跟上面一样

package com.yjj.serialize.serialize;

import com.yjj.serialize.object.User;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:06
 * @Version: 1.0
 */
public class SerializeTest2 {
    public static void main(String[] args) {
        User user = new User("殷俊杰",16);
        try {
            serialize(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void serialize(Object object) throws IOException {
        Class clazz = object.getClass();
        System.out.println(clazz.getName());
        File file = new File("E:/b.txt");
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(object);
            System.out.printf("序列化对象%s成功",clazz.getName());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (objectOutputStream != null) {
                objectOutputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        }
    }
}

反序列化

package com.yjj.serialize.deserialize;

import com.yjj.serialize.object.User;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:45
 * @Version: 1.0
 */
public class DeserializeTest2 {
    public static void main(String[] args) {
        File file=new File("E:/b.txt");
        Object object=deserialize(file);
        User user= (User) object;
        System.out.println(user);
    }

    public static Object deserialize(File file){
        try {
            ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
            Object object=objectInputStream.readObject();
            return object;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

此时是正常且成功的,但是,我们如果现在在User对象新增一个属性

package com.yjj.serialize.object;

import java.io.Serializable;

/**
 * @Description:
 * @Author: yinjunjie
 * @CreateDate: 2018/8/31 21:36
 * @Version: 1.0
 */
public class User implements Serializable{
    private String name;
    private int age;
    private String gender;

    public User(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

上面我说,serialVersionUID是根据对象信息生成的,现在对象多了一个属性,信息已经改变,再次生成的serialVersionUID 已经和刚才的不一样了,这时我们再进行反序列化会报如下错误

D:\software\jdk\bin\java -javaagent:D:\software\idea\lib\idea_rt.jar=10101:D:\software\idea\bin -Dfile.encoding=UTF-8 -classpath D:\software\jdk\jre\lib\charsets.jar;D:\software\jdk\jre\lib\deploy.jar;D:\software\jdk\jre\lib\ext\access-bridge-64.jar;D:\software\jdk\jre\lib\ext\cldrdata.jar;D:\software\jdk\jre\lib\ext\dnsns.jar;D:\software\jdk\jre\lib\ext\jaccess.jar;D:\software\jdk\jre\lib\ext\jfxrt.jar;D:\software\jdk\jre\lib\ext\localedata.jar;D:\software\jdk\jre\lib\ext\nashorn.jar;D:\software\jdk\jre\lib\ext\sunec.jar;D:\software\jdk\jre\lib\ext\sunjce_provider.jar;D:\software\jdk\jre\lib\ext\sunmscapi.jar;D:\software\jdk\jre\lib\ext\sunpkcs11.jar;D:\software\jdk\jre\lib\ext\zipfs.jar;D:\software\jdk\jre\lib\javaws.jar;D:\software\jdk\jre\lib\jce.jar;D:\software\jdk\jre\lib\jfr.jar;D:\software\jdk\jre\lib\jfxswt.jar;D:\software\jdk\jre\lib\jsse.jar;D:\software\jdk\jre\lib\management-agent.jar;D:\software\jdk\jre\lib\plugin.jar;D:\software\jdk\jre\lib\resources.jar;D:\software\jdk\jre\lib\rt.jar;D:\workspace\demo-all\serialize\target\classes com.yjj.serialize.deserialize.DeserializeTest2
null
java.io.InvalidClassException: com.yjj.serialize.object.User; local class incompatible: stream classdesc serialVersionUID = 6219457185932359615, local class serialVersionUID = 7483460167538010623
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
    at com.yjj.serialize.deserialize.DeserializeTest2.deserialize(DeserializeTest2.java:27)
    at com.yjj.serialize.deserialize.DeserializeTest2.main(DeserializeTest2.java:19)

Process finished with exit code 0

我们指定serialVersionUID后就可以随意修改了

网络编程发送对象示例

总结

  1. 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。
  2. 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)
  3. serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
  4. 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID
    进行对比,如果这两个id不一致,反序列则失败。
  5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
    在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。
  6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。
  7. 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。

相关文章

网友评论

      本文标题:序列化与反序列化

      本文链接:https://www.haomeiwen.com/subject/nnwavftx.html