序列化
序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。
为什么要把Java对象序列化呢?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。
要实现序列化,需要实现 java.io.Serializable 接口,这里采用的 output 或 input 是相对于Java对象的:从Java对象写入流,就是output,从流写入Java对象就是 input
写入流
从Java对象变成byte[]数组,需要 ObjectOutputStream ,它负责把一个Java对象写入一个字节流:
public class OutputStreamTest {public static void main(String[] args) {ByteArrayOutputStream buffer = new ByteArrayOutputStream();try(ObjectOutputStream output=new ObjectOutputStream(buffer)){output.writeInt(123);output.writeUTF("1234");output.writeObject(Double.valueOf(123.456));System.out.println(Arrays.toString(buffer.toByteArray()));}catch (Exception e){e.printStackTrace();}}}
ObjectOutputStream既可以写入基本类型,如int,boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。
因为写入Object时需要大量的类型信息,所以写入的内容很大。
当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个.ser 扩展名。
反序列化
和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:
try (ObjectInputStream input = new ObjectInputStream(...)) {int n = input.readInt();String s = input.readUTF();Double d = (Double) input.readObject();}
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
安全性
因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。
小结
可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface);
反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需);
Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。
serialVersionUID
简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
