MENU

序列化与反序列化(一)序列化初窥

June 30, 2018 • Code

前言:序列化与反序列化在 Java 及其他主流语言中是一个比较常见的概念,掌握其使用方法及相关概念是必要的,本文通过源码和栗子学习 Java 中序列化概念及使用。

序列化概览

什么是序列化与反序列化

来自百科:

序列化(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

简单来说,序列化就是「保存对象状态信息」的过程。它的结果是产生便于网络传输或存储的二进制(文件或流),可以是 JSON、XML 甚至是二进制流,其相同点在于与平台无关、与语言无关,即任何语言都可读取。所以本质上,序列化也可看成一种「重编码」过程:将对象状态重新「编码」成其它易于传输、平台无关的格式。

反序列化则是序列化的逆过程:即将平台无关二进制编码转为对象。

如果没有序列化,那想在远程主机间共享对象状态是一件十分困难的事件,因为需要直接对原始字节流操作。

序列化的作用 & 应用场景

  • 序列化通常应用于网络传输及 RMI(远程方法调用)中。
  • 需要临时保存信息到磁盘中的场景,如 session 的活化与钝化。

session 的钝化指当遇到一些情况(服务器关闭、内存不足等)需要将内存中 session 保存到磁盘中,活化则相反,这是一对典型的序列化(钝化)与反序列化(活化)过程。

序列化条件

如果一个对象要被序列化,需具备 2 个条件:

  • 该类必须实现序列化接口。
  • 该类中所有属性必须可序列化,引用类型属性则对应的类也必须可序列化。如果不可序列化,则属性必须注明是 transient。

transient 关键字表明该属性不参与序列化。

以上两个条件不满足都会抛出 NotSerializableException 异常,无法进行序列化。

序列化接口

Java 中序列化相关的接口主要有两个:SerializableExternalizable

  • Serializable:一个类如果需要被序列化,可以实现 Serializable 接口。该接口源码如下:
public interface Serializable {
}

可见 Serializable 接口中没有方法,实现它仅标识可序列化的语义,真正实现序列化的是对象输入/输出流中。

  • Externalizable:Serializable 的子类,增加了 writeExternalreadExternal 方法,用来自定义需要被序列化的属性。
public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

序列化及反序列化使用方法

Java 中 序列化的使用主要有两种方式:原生 Java 方式框架包装调用。框架调用具体可参考各框架的官方手册。下面栗子为原生 Java 方式。

需要被序列化的类 User

import java.io.Serializable;
import java.util.Date;

/**
 * @author kbrx93
 * 序列化 POJO 对象
 */
public class User implements Serializable {

    private static final long serialVersionUID = -2258453247170887114L;
    private long userId;
    private String userName;
    private int age;
    transient private Date date;
    
    public User(long userId, String userName, int age, Date date) {
        this.userId = userId;
        this.userName = userName;
        this.age = age;
        this.date = date;
    }
    
    // getter 和 setter 方法
    ...
    ...
    
    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", age=" + age +
                ", date=" + date +
                '}';
    }
}

具体序列化与反序列化过程

序列化

public class SerializableDemo {

    public static void main(String[] args) {
        // 1. 实例化对象并设置属性
        User user = new User(1L, "kbrx93", 50, new Date());

        // 2. 实例化对象输出流,并将 User 对象序列化到文件中
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.ser"))) {
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

结果(序列化后的文件 User.ser):

反序列化

public class SerializableDemo {

    public static void main(String[] args) {
        // 1. 实例化对象输入流
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.ser"))) {
            // 2. 从文件中读取对象状态信息并构造对象
            User user =(User) ois.readObject();
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:

自定义序列化

如果一个对象需要控制序列化过程(如动态改变数值),可以在被序列化的类中增加 writeObjectreadObject 方法,这两个方法可以自定义类的序列化过程。

如果类中实现了这两个方法,在序列化与反序列化时 JVM 会自动调用这两个方法,否则默认调用对象输出流的 defaultWriteObject 和对象输入流的 defaultReadObject 方法。

序列化的不足

虽然序列化在远程调用、临时存储、网络传输方面有优势,但其也存在不足,主要集中于:

  • 性能开销大
  • 安全性不足
  • 无法跨语言

不同序列化框架对于序列化的优化程度及开销需求不同。

小结

对序列化有初步的了解:

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

  • 序列化的条件

  • 序列化接口

  • 序列化与反序列化的使用

  • 自定义序列化

  • 序列化的不足

参考

Last Modified: July 30, 2018
Archives QR Code
QR Code for this page
Tipping QR Code