Java ---- 序列化

Java对象的序列化

  • Java平台容许咱们在内存中建立可复用的Java对象,但通常状况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM中止运行以后可以保存(持久化)指定的对象,并在未来从新读取被保存的对象。Java对象序列化就可以帮助咱们实现该功能。java

  • 使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在将来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量程序员

  • 简而言之,就是让对象想基本数据类型和字符串类型同样,经过输入输出字节流ObjectInputStream 和 ObjectOutputStream进行写和读操做。数据库

Java序列化的应用场景

  • 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候数组

  • 当你想用套接字在网络上传送对象的时候网络

  • 当你想经过RMI传输对象的时候ide

如何对Java对象进行序列化与反序列化

在Java中,只要一个类实现了java.io.Serializable接口,那么它就能够被序列化。函数

import java.io.*;
import java.util.*;

class User implements Serializable {
    private String name;
    private int age;
    private Date birthday;
    private transient String gender;
    private static int test =1;
    private static final long serialVersionUID = -6849794470754667710L;

    public User() {
        System.out.println("none-arg constructor");
    }

    public User(String name, Integer age,Date birthday,String gender) {
        System.out.println("arg constructor");
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        this.gender = gender;
    }

    public void setTest(int Newtest) {
        this.test = Newtest;
    }

    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;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

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

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

public class SerializableDemo {
    public static void main(String[] args) throws Exception {
        //Initializes The Object
        User user = new User("qiuyu",23,new Date(),"male");
        System.out.println(user);
        user.setTest(10);
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));
        out.writeObject(user);
        out.close();

        //Read Obj from File
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));
        User newUser = (User) in.readObject();
        System.out.println(newUser);
        in.close();
    }
}

此时的输出为: 测试

arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@4883b407
  • 此时必须注意的是,当从新读取被保存的User对象时,并无调用User的任何构造器,看起来就像是直接使用字节将User对象还原出来的。this

  • 当User对象被保存到tempfile文件中以后,咱们能够在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有User.class,不然会抛出ClassNotFoundException。code

Q:以前不是说序列化不保存静态变量么,为何这里的静态变量进行了传递,都变成了10呢?
A:由于此时User.class已经被加载进了内存中,且将static变量test从1更改成了10。当咱们恢复对象时,会直接获取当前static的变量test的值,因此为10。

Q:但若是咱们的恢复操做在另外一个文件中进行,结果会怎么样呢?

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Recovering {
    public static void main(String[] args) throws Exception{
        //Read Obj from File
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));
        User newUser = (User) in.readObject();
        System.out.println(newUser);
        in.close();
    }
}

输出结果为:

User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=1} User@6442b0a6

A:由于在运行此代码时,User.class会被加载进内存,而后执行初始化,将被初始化为1,所以test变量会被恢复为1。注意区分上面两种状况,不要由于是在本地进行测试,就认为static会被序列化,同时要了解Java运行时,内存加载的机制。

基本知识点

Serializable接口

  • 对于任何须要被序列化的对象,都必需要实现接口Serializable,它只是一个标识接口自己没有任何成员,只是用来标识说明当前的实现类的对象能够被序列化.

  • 若是父类实现序列化,子类自动实现序列化,不须要显式实现Serializable接口。

  • 若是被写对象的类型是String数组EnumSerializable,那么就能够对该对象进行序列化,不然将抛出NotSerializableException。

对象的读写

  • Java类中对象的序列化工做是经过 ObjectOutputStream ObjectInputStream 来完成的。

  • 使用readObject()writeObject()方法对对象进行读写操做;

  • 对于基本类型,可使用readInt()writeInt() , readDouble()writeDouble()等相似的接口进行读写。

序列化机制

  • 若是仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不只会序列化当前对象自己,还会对该对象引用的其它对象也进行序列化,一样地,这些其它对象引用的另外对象也将被序列化。

  • 在现实应用中,有些时候不能使用默认序列化机制。好比,但愿在序列化过程当中忽略掉敏感数据,或者简化序列化过程。为此须要为某个字段声明为transient,那么序列化机制就会忽略被transient修饰的字段。transient的引用变量会以null返回,基本数据类型会以相应的默认值返回。(例如:引用类型没有实现Serializable,或者动态数据只能够在执行时求出而不能或没必要存储)

writeObject()与readObject()

  • 对于上被声明为transient的字段gender,除了将transient关键字去掉以外,是否还有其它方法能使它再次可被序列化?方法之一就是在User类中添加两个方法:writeObject()与readObject(),以下所示:

private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeUTF(gender);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        gender = in.readUTF();
    }
  • 在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,此时会忽略掉gender字段。

  • 而后再调用writeUtf()方法显示地将gender字段写入到ObjectOutputStream中。readObject()的做用则是针对对象的读取,其原理与writeObject()方法相同。

Q: writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?
A: 毫无疑问,是使用反射。(注意这不是继承接口的方法,由于接口类的方法都是public的,而这里的方法是private的)

Externalizable接口

  • 不管是使用transient关键字,仍是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另外一个序列化接口Externalizable,使用该接口以后,以前基于Serializable接口的序列化机制就将失效。

import java.io.*;
import java.util.*;

class UserExtern implements Externalizable {
    private String name;
    private int age;
    private Date birthday;
    private transient String gender;
    private static int test =1;
    private static final long serialVersionUID = -6849794470754667710L;

    public UserExtern() {
        System.out.println("none-arg constructor");
    }

    public UserExtern(String name, Integer age,Date birthday,String gender) {
        System.out.println("arg constructor");
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        this.gender = gender;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeUTF(gender);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        gender = in.readUTF();
    }


    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
        out.writeObject(birthday);
        out.writeUTF(gender);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        age = in.readInt();
        birthday = (Date) in.readObject();
        gender = in.readUTF();
    }


    public void setTest(int Newtest) {
        this.test = Newtest;
    }

    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;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

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

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



public class ExternalizableDemo {
    public static void main(String[] args) throws Exception {
        UserExtern userExtern = new UserExtern("qiuyu",23,new Date(),"male");
        System.out.println(userExtern);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("external"));
        out.writeObject(userExtern);
        out.close();


        ObjectInputStream in = new ObjectInputStream(new FileInputStream("external"));
        UserExtern userExtern1 = (UserExtern)in.readObject();
        System.out.println(userExtern1)
    }
}

输出结果为:

arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@25618e91
none-arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@604ed9f0
  • Externalizable继承于Serializable,当使用该接口时,序列化的细节须要由程序员去完成writeExternal()readExternal()方法的具体细节,以及哪些状态须要进行序列化。

  • 另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去建立一个新的对象,而后再将被保存对象的字段的值分别填充到新对象中。这就是为何在此序列化过程当中UserExtern的无参构造器会被调用。因为这个缘由,实现Externalizable接口的类必需要提供一个无参的构造器,且它的访问权限为public

注意事项

  • 读取对象的顺序必须与写入的顺序相同

  • 若是有不能被序列化的对象,执行期间就会抛出NotSerializableException异常

  • 序列化时,只对对象的状态进行保存,而无论对象的方法

  • 静态变量不会被序列化,由于全部的对象共享同一份静态变量的值

  • 若是一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存还原,并且会是递归的方式(对象网)。(序列化程序会将对象版图上的全部东西储存下来,这样才能让该对象恢复到原来的状态)

  • 若是子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,不然会抛InvalidClassException异常;由于反序列化时会恢复原有子对象的状态,而父类的成员变量也是原有子对象的一部分。因为父类没有实现序列化接口,即便没有显示调用,也会默认执行父类的无参构造函数使变量初始化;

深刻理解

序列化ID的问题

  • serialVersionUID适用于JAVA的序列化机制。简单来讲,Java的序列化机制是经过判断类的serialVersionUID来验证版本一致性的。

  • 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,若是相同就认为是一致的,能够进行反序列化,不然就会出现序列化版本不一致的异常,便是InvalidCastException。

序列化存储规则

  • Java 序列化机制为了节省磁盘空间,具备特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用;

  • 序列化到同一个文件时,如第二次修改了相同对象属性值再次保存时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,所以只保存第二次写的引用,因此读取时,都是第一次保存的对象,第二次进行的修改将无效。

public static void main(String[] args) throws Exception {
        //Initializes The Object
        User user = new User("qiuyu",23,new Date(),"male");
        user.setTest(10);
        System.out.println(user);


        //Write Obj to File
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));
        out.writeObject(user);
        user.setAge(25);
        System.out.println(user);
        out.writeObject(user);
        out.close();

        //Read Obj from File
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));
        User newUser = (User) in.readObject();
        User newUser1 = (User) in.readObject();
        System.out.println(newUser);
        System.out.println(newUser1);
        in.close();
    }

输出结果: 注意观察age的值

User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=25, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407

屡次序列化的问题

  • 在一次的序列化的过程当中,ObjectOutputStream 会在文件开始的地方写入一个 Header的信息到文件中。因而在屡次序列化的过程当中就会继续在文件末尾(本次序列化的开头)写入 Header 的信息,这时若是进行反序列化的对象的时候会报错:java.io.StreamCorruptedException: invalid type code: AC

相关文章
相关标签/搜索