阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

序列化

35次阅读
没有评论

共计 2341 个字符,预计需要花费 6 分钟才能阅读完成。

序列化是指把一个 Java 对象变成二进制内容,本质上就是一个 byte[] 数组。

为什么要把 Java 对象序列化呢?因为序列化后可以把 byte[] 保存到文件中,或者把 byte[] 通过网络传输到远程,这样,就相当于把 Java 对象存储到文件或者通过网络传输出去了。

有序列化,就有反序列化,即把一个二进制内容(也就是 byte[] 数组)变回 Java 对象。有了反序列化,保存到文件中的 byte[] 数组又可以“变回”Java 对象,或者从网络上读取 byte[] 并把它“变回”Java 对象。

我们来看看如何把一个 Java 对象序列化。

一个 Java 对象要能序列化,必须实现一个特殊的 java.io.Serializable 接口,它的定义如下:

public interface Serializable {
}

Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

序列化

把一个 Java 对象变为 byte[] 数组,需要使用ObjectOutputStream。它负责把一个 Java 对象写入一个字节流:

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

public class Main {public static void main(String[] args) throws IOException {ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {// 写入 int:
            output.writeInt(12345);
            // 写入 String:
            output.writeUTF("Hello");
            // 写入 Object:
            output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
    }
}

ObjectOutputStream既可以写入基本类型,如 intboolean,也可以写入String(以 UTF- 8 编码),还可以写入实现了Serializable 接口的Object

因为写入 Object 时需要大量的类型信息,所以写入的内容很大。

反序列化

ObjectOutputStream 相反,ObjectInputStream负责从一个字节流读取 Java 对象:

try (ObjectInputStream input = new ObjectInputStream(...)) {int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();}

除了能读取基本类型和 String 类型外,调用 readObject() 可以直接返回一个 Object 对象。要把它变成一个特定类型,必须强制转型。

readObject()可能抛出的异常有:

  • ClassNotFoundException:没有找到对应的 Class;
  • InvalidClassException:Class 不匹配。

对于 ClassNotFoundException,这种情况常见于一台电脑上的 Java 程序把一个 Java 对象,例如,Person 对象序列化以后,通过网络传给另一台电脑上的另一个 Java 程序,但是这台电脑的 Java 程序并没有定义 Person 类,所以无法反序列化。

对于 InvalidClassException,这种情况常见于序列化的Person 对象定义了一个 int 类型的 age 字段,但是反序列化时,Person类定义的 age 字段被改成了 long 类型,所以导致 class 不兼容。

为了避免这种 class 定义变动导致的不兼容,Java 的序列化允许 class 定义一个特殊的 serialVersionUID 静态变量,用于标识 Java 类的序列化“版本”,通常可以由 IDE 自动生成。如果增加或修改了字段,可以改变 serialVersionUID 的值,这样就能自动阻止不匹配的 class 版本:

public class Person implements Serializable {private static final long serialVersionUID = 2709425275741743919L;
}

要特别注意反序列化的几个重要特点:

反序列化时,由 JVM 直接构造出 Java 对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

安全性

因为 Java 的序列化机制可以导致一个实例能直接从 byte[] 数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的 byte[] 数组被反序列化后可以执行特定的 Java 代码,从而导致严重的安全漏洞。

实际上,Java 本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过 JSON 这样的通用数据结构来实现,只输出基本类型(包括 String)的内容,而不存储任何与代码相关的信息。

小结

可序列化的 Java 对象必须实现 java.io.Serializable 接口,类似 Serializable 这样的空接口被称为“标记接口”(Marker Interface);

反序列化时不调用构造方法,可设置 serialVersionUID 作为版本号(非必需);

Java 的序列化机制仅适用于 Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如 JSON。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2341字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中