java序列化

628 阅读4分钟

1. 定义

序列化:将对象写入到IO流中。

反序列化:从IO流中恢复对象。

2. 用途

对象序列化之后,便可以将其保存在磁盘之中,或者通过网络传输。

在程序运行的过程中,所有的变量都是在内存中,比如,定义一个dict:

d = dict(name='Bob', age=20, score=88)

可以随时修改变量,比如把name改成'Bill',但是一旦程序结束,变量所占用的内存就被操作系统全部回收。如果没有把修改后的'Bill'存储到磁盘上,下次重新运行程序,变量又被初始化为'Bob'

我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling

3. 实现

如果对象想要序列化,必须实现Serializable接口或者Externalizable接接口。

3.1. Serializable

Serializable接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的。

3.1.1.1. 对象实现Serializable接口

public class Person implements Serializable {

    private String name;
    private int age;
    private String sex;


    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 String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

3.1.1.2. 执行序列化,并将对象写入磁盘

public static void main(String[] args) throws IOException, ClassNotFoundException {
    ObjectOutputStream objectOutputStream = null;
    try {
        Person person = new Person();
        objectOutputStream = new ObjectOutputStream(new FileOutputStream("G:\\person01.txt"));
        person.setName("小明");
        person.setAge(4);
        objectOutputStream.writeObject(person);

    } finally {
        if (objectOutputStream != null) {
            try {
                objectOutputStream.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

3.1.1.3. 反序列化

public static void main(String[] args) throws IOException, ClassNotFoundException {
    ObjectOutputStream objectOutputStream = null;
    try {

        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("G:\\person01.txt"));
        Object o = objectInputStream.readObject();
        System.out.println("o = " + o.toString());
    } finally {
        if (objectOutputStream != null) {
            try {
                objectOutputStream.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

3.2. Externalizable

实现Externalizable接口,必须实现writeExternalreadExternal方法


/**
 * @author h
 * @date Created in 2020/3/19 21:25
 */
public class Dog implements Externalizable {

    private String name;
    private int age;
    private String sex;


    /**
     * The object implements the writeExternal method to save its contents
     * by calling the methods of DataOutput for its primitive values or
     * calling the writeObject method of ObjectOutput for objects, strings,
     * and arrays.
     *
     * @param out the stream to write the object to
     * @throws IOException Includes any I/O exceptions that may occur
     * @serialData Overriding methods should use this tag to describe
     * the data layout of this Externalizable object.
     * List the sequence of element types and, if possible,
     * relate the element to a public/protected field and/or
     * method of this Externalizable class.
     */
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.name);
        out.writeInt(this.age);
        out.writeObject(this.sex);
    }

    /**
     * The object implements the readExternal method to restore its
     * contents by calling the methods of DataInput for primitive
     * types and readObject for objects, strings and arrays.  The
     * readExternal method must read the values in the same sequence
     * and with the same types as were written by writeExternal.
     *
     * @param in the stream to read data from in order to restore the object
     * @throws IOException            if I/O errors occur
     * @throws ClassNotFoundException If the class for an object being
     *                                restored cannot be found.
     */
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

        //注意,这里的属性与上面writeExternal应该保持一致的顺序
        this.name = (String) in.readObject();
        this.age = in.readInt();
        this.sex = (String) in.readObject();
    }

    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 String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

}

3.3. transient关键字

使用transient修饰的属性,序列化时会忽略掉此字段。

反序列化出的对象,被transient修饰的属性的值为默认值。

3.4. serialVersionUID 序列化版本号

private static final long serialVersionUID = 247875550322L;

序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。 如果接收者已为该对象加载了一个与相应发送者类具有不同的serialVersionUID的类,则反序列化将导致InvalidClassException

建议手动声明serialVersionUID,进行对象版本控制。

如果类中没有明确声明serialVersionUID时 ,程序就会根据类的信息,生成一个默认的serialVersionUID。但这个默认的serialVersionUID是不稳定的:

  1. 默认的serialVersionUID计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不同。
  2. 当类的信息有改变时,生成的serialVersionUID也会改变。

4. 注意事项

  1. 如果类的成员为引用类型,那这个引用类型也必须是可序列化的。

5. 参考

java序列化,看这篇就够了

Java serialVersionUID 有什么作用?

序列化