2021.6.23:序列化/Serialization
概念
关于序列化,我曾在Python序列化一节说过,现在再复习一遍。
序列化:把内存中的数据(我们看到的Java对象)变为存储/传输时的二进制数据,即byte [ ] 数组。
反序列化:把之前的byte[ ]数组重新变成Java对象。
模块:java.io.Serializable
这是一个接口,并且其中没有任何方法,即空接口。这样的空接口称之为“标记接口”( Marker Interface ),实现标记接口的类只是给自身贴了个“标记”,并没有增加任何方法。
序列化
java.io.ObjectOutputStream
用ObjectOutputStream将一个Java对象转化为byte[ ]。它负责把一个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、String、Object output.writeInt(12345); output.writeUTF("Hello"); output.writeObject(Double.valueOf(123.456)); } System.out.println(Arrays.toString(buffer.toByteArray())); } } [-84, -19, 0, 5, 119, 11, 0, 0, ... ,-108, -32, -117, 2, 0, 0, ... , 119]
ObjectOutputStream可以写入基本类型、String、实现了Serializable接口的Object。
由于写入Object类型需要大量类型信息,所以写入的内容很多。
反序列化
java.io.ObjectInputStream
从一个byte[ ]读取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
在一台PC上的一个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=2709425254321887; }
注意
反序列化时,由JVM直接构造出Java对象,不调用构造方法,因此构造方法内部的代码,在反序列化时根本不会执行。
安全性
因为Java的序列化机制可以导致一个实例能直接从byte[ ]创建,而不经过构造方法,因此它存在一定的安全隐患。
更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型和String的内容,而不存储任何代码相关的内容。
此外,Java序列化机制只适用于Java,如果要和其它语言交换数据,需要使用通用序列化方法,如JSON。

浙公网安备 33010602011771号