1 Java序列化的概念
1.1 什么是序列化
序列化就是将对象转换为可以存储或传输的形式,以实现对象持久化存储到磁盘中,或者在网络中传输。要将对象转为可以存储或传输的形式,首先就要用一种统一的标准来描述对象,这样序列化的对象才能被反序列化出来。不同的序列化算法就是用不同的标准来描述对象,序列化算法很多,这里举几个常见的:
- json
- xml
- yaml
- Java Serialization
前三种见名知意,就是分别用json、xml、yaml来描述对象,第四种Java Serialization是JDK默认的序列化算法,其使用了一种称为 Java Object Serialization Stream Protocol 的二进制格式来描述JAVA对象。
2 JAVA中的序列化
JDK种提供了Serializable接口用来声明哪些类可以被序列化,提供了ObjectOutputStream、ObjectOutputStream来进行序列化和反序列化。
JAVA的序列化中有几个注意点:
- 成员变量必须可序列化 如果所要序列化的对象的成员属性中含有对其他对象的引用,要求所引用的对象也必须是可序列化的(实现serializable接口),否则会序列化失败。
- transient关键字,可避免被序列化 属性值不会被序列化出去,其会是默认值,如String类型将为null
- 无法更新状态 由于java序利化算法不会重复序列化同一个对象,如果对象的内容更改后,再次序列化,并不会再次将此对象转换为字节序列。
- serialVersionUID 序列化版本号,类似于乐观锁中的版本号,用来保证序列化后的字节序列没有被改动过,反序列化回来后和原来的程序是兼容的。 serialVersionUID不会自动改变,而是留给程序员手动更改的一个版本号标志位。更改了序列化文件的程序员一并更改版本号提示后来的人文件被更改过。 如果在反序列化时,类的 serialVersionUID 与序列化时的版本号不匹配,那么会抛出 InvalidClassException 异常,表示类的版本不兼容,无法进行反序列化。
3 JDK序列化算法
Java Object Serialization Stream Protocol规定整个对象序列化后的文件由三部分组成:
- 头部(Header):包含魔数(Magic Number)和版本号(Version Number)。魔数标识了该流是 Java 序列化流,版本号用于指定序列化协议的版本。
- 类描述符表(Class Descriptor Table):包含了序列化流中所引用的类的描述符信息。每个类描述符包括类的名称、序列化编号、序列化版本号等信息。
- 对象数据(Object Data):按照序列化顺序包含了被序列化对象的状态信息。这包括了对象的实例变量、类信息等。
4 序列化和反序列化的实现方式
Java 中的序列化和反序列化可以通过实现 Serializable 接口来完成。Serializable 是一种标记接口,它没有方法定义,但它具有一个特别的作用,就是用于在描述 java 类可序列化时做类型判断的信息。当一个类实现 Serializable 接口时,表明这个类是可序列化的。Serializable 接口只是一个标识接口,我们并不需要重载任何方法。
在实现 Serializable 接口后,就可以通过 ObjectOutputStream 来将对象序列化,并将序列化后的字节流输出到文件或网络中;同时,也可以通过 ObjectInputStream 来将序列化后的字节流反序列化成对象。 java.io.ObjectOutputStream 继承自 OutputStream 类,因此可以将序列化后的字节序列写入到文件、网络等输出流中。
来看 ObjectOutputStream 的构造方法: ObjectOutputStream(OutputStream out)
一个对象要想序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable 接口open in new window,否则会抛出NotSerializableException 。
- 该类的所有字段都必须是可序列化的。如果一个字段不需要序列化,则需要使用transient 关键字open in new window进行修饰。
- 该构造方法接收一个 OutputStream 对象作为参数,用于将序列化后的字节序列输出到指定的输出流中。
示例代码如下:
import java.io.*;
public class SerializationDemo {
public static void main(String[] args) {
// 序列化对象
Person person = new Person("Tom", 20);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.txt"));
objectOutputStream.writeObject(person);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化对象
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.txt"));
Person restoredPerson = (Person) objectInputStream.readObject();
System.out.println(restoredPerson);
objectInputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
在上述代码中,我们定义了一个 Person 类,该类实现了 Serializable 接口。在序列化过程中,我们使用 ObjectOutputStream 类将 person 对象写出到文件中;在反序列化过程中,我们使用 ObjectInputStream 类读取文件中的字节流,并将其转换为 Person 对象。
5 序列化和反序列化的优点和缺点
序列化和反序列化的优点是:
- 对象的序列化方便了对象在不同应用之间的传递、存储和恢复。
- 通过序列化可以实现分布式计算,在不同的机器上对同一对象进行操作和协作。
- 序列化提供了数据持久化的能力,即将对象的状态保存在硬盘等介质中,下次可以直接从硬盘中读取数据,避免了频繁地进行数据库读写操作。
序列化和反序列化的缺点是:
- 在进行序列化和反序列化操作时,需要消耗额外的时间和开销,特别是当对象比较大或者嵌套较深的时候,可能会导致严重的性能问题。
- 序列化和反序列化可能存在安全性问题,如果被攻击者篡改了序列化后的字节流数据,那么反序列化后的对象可能会出现意外行为,如获得不应该获得的权限。
本文暂时没有评论,来添加一个吧(●'◡'●)