在本文中,我们将解释什么是序列化以及它在 Java 中的工作原理。
图 1. 线程的图形表示
Stream主要分为两种:
介绍
对象序列化是对象存储其自身以及它使用输出流引用的任何其他对象的完整副本的能力(例如,存储到外部文件)。这样,稍后需要时可以从序列化(保存的)副本重新创建对象。对象序列化是JDK 1.1中引入的新功能,提供了将组或单个对象转换为比特流或字节数组的功能,以便通过网络存储或传输。如上所述,给定的位流或字节数组可以转换回 Java 对象。这主要归功于ObjectInputStream
和类而自动发生ObjectOutputStream
。程序员可以Serializable
在创建类时决定通过实现接口来实现此功能。序列化过程也称为对象封送,而反序列化过程称为反封送。序列化是一种机制,允许对象使用ObjectOutputStream
. 保存的对象可以是数据结构、图表、类对象JFrame
或任何其他对象,无论其类型如何。同时,序列化存储有关对象类型的信息,以便稍后在反序列化时,该信息用于重新创建对象的确切类型。因此,序列化提供了以下功能:
- 用于存储对象的系统,即将其属性保存到外部文件、磁盘或数据库。
- 远程过程调用系统。
- 对象分发系统,例如COM、COBRA等软件组件中的对象分发系统。
- 用于识别变量数据随时间变化的系统。
流:
每个程序都必须将其数据写入存储位置或管道,并且每个程序都必须从管道或存储位置读取数据。在 Java 中,程序写入和读取数据的这些通道称为Streams (Stream
)。
- 称为 *Streams 的字节流类
- 称为 *Reader 和 *Writer 的字符流类
坚持
对象持久性是对象生存的能力,换句话说,是在程序执行过程中“生存”的能力。这意味着,只要不再使用运行时创建的任何对象,JVM 清除程序就会销毁该对象。但如果实现了持久化 API,这些对象就不会被 JVM scavenger 销毁,而是被允许“存活”,这也使得下次应用程序启动时可以访问它们。换句话说,持久性意味着对象有一个生命周期,与正在运行的应用程序的生命周期无关。实现持久性的一种方法是将对象存储在外部文件或数据库中的某个位置,然后稍后使用这些文件或数据库作为源来恢复它们。这就是序列化发挥作用的地方。只要 JVM 运行,任何非持久对象就存在。序列化对象只是转换为流的对象,然后将其保存到外部文件或通过网络传输以进行存储和恢复。Serialized接口的实现
任何类都必须实现一个接口java.io.Serializable
来序列化该类的对象。该接口Serializable
没有方法,仅标记类,以便将其标识为可序列化。只能保存序列化类对象的字段。方法或构造函数不存储为序列化流的一部分。如果任何对象充当对另一个对象的引用,那么如果该对象的类实现了该接口,则该对象的字段也会被序列化Serializable
。换句话说,这样得到的这个对象的图是完全可序列化的。对象图包括对象及其子对象的字段树或结构。帮助实现该接口的两个主要类Seriliazable
:
ObjectInputStream
ObjectOutputStream
import java.io.*;
public class RandomClass implements Serializable {
// Генерация рандомного значения
private static int r() {
return (int)(Math.random() * 10);
}
private int data[];
// Конструктор
public RandomClass() {
datafile = new int[r()];
for (int i=0; i<datafile.length; i++)
datafile[i]=r();
}
public void printout() {
System.out.println("This RandomClass has "+datafile.length+" random integers");
for (int i=0; i<datafile.length; i++) {
System.out.print(datafile[i]+":");
System.out.println();
}
}
在上面的代码中,创建了一个可序列化的类,因为 由序列化接口“标记”。当创建该类的实例时,该类会创建一个随机整数数组。下面的代码显示了使用ObjectOutputStream
. 该程序有一个整数数组,但对于序列化,我们不必迭代其内部对象。界面Seriliazable
会自动处理这个问题。 清单 2. 序列化对象以输出到文件的简单示例
import java.io.*;
import java.util.*;
public class OutSerialize {
public static void main (String args[]) throws IOException {
RandomClass rc1 = new RandomClass();
RandomClass rc2 = new RandomClass();
//создание цепи потоков с потоком вывода an object в конце
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objects.dat"));
Date now = new Date(System.currentTimeMillis());
//java.util.* был импортирован для использования класса Date
out.writeObject(now);
out.writeObject(rc1);
out.writeObject(rc2);
out.close();
System.out.println("I have written:");
System.out.println("A Date object: "+now);
System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
}
}
下面的代码演示了该类的功能ObjectInputStream
,它将序列化数据从外部文件读取到程序中。请注意,对象的读取顺序与写入文件的顺序相同。 清单 3. 读取序列化对象或反序列化
import java.io.*;
import java.util.*;
public class InSerialize {
public static void main (String args[]) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream (new FileInputStream("objects.dat"));
Date d1 = (Date)in.readObject();
RandomClass rc1 = (RandomClass)in.readObject();
RandomClass rc2 = (RandomClass)in.readObject();
System.out.println("I have read:");
System.out.println("A Date object: "+d1);
System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
}
}
几乎所有 Java 类都可以序列化,包括 AWT 类。框架是一个窗口,包含一组图形组件。如果帧被序列化,则序列化引擎会处理此问题并序列化其所有组件和数据(位置、内容等)。某些 Java 类对象无法序列化,因为它们包含引用临时操作系统资源的数据。例如类java.io.FileInputStream
和java.lang.Thread
. 如果一个对象包含对不可序列化元素的引用,则整个序列化操作将失败并抛出异常NotSerializableException
。如果任何对象引用未序列化对象的引用,则可以使用瞬态关键字将其序列化。 清单 4. 使用瞬态关键字创建可序列化对象
public class Sclass implements Serializable{
public transient Thread newThread;
//помните, что поток(поток параллельного исполнения) по умолчанию не сериализуемый класс
private String studentID;
private int sum;
}
序列化的安全性
在 Java 中序列化一个类涉及通过流将其所有数据传递到外部文件或数据库。我们可以随时限制要序列化的数据。有两种方法可以做到这一点:- 每个声明为瞬态的类参数都不会被序列化(默认情况下,所有类参数都会被序列化)
- 或者,我们要序列化的每个类参数都标有一个标签
Externalizable
(默认情况下,不序列化任何参数)。
ObjectOutputStream
当在对象上调用时,如果该对象的数据字段被标记为瞬态,则数据字段将不会被序列化。例如:private transient String password
。另一方面,要显式地将对象的数据声明为可序列化,我们必须将该类标记为ExternalizablewriteExternal
显式readExteranl
写入和读取该对象的数据。
结论
对象序列化的特性在许多分布式系统中被用作传输数据的一种方式。但序列化揭示了隐藏的细节,从而破坏了抽象数据类型的真实性,进而破坏了封装性。同时,很高兴知道序列化对象的数据与原始对象中的数据相同。这也是实现接口ObjectInputValidation
和重写方法的绝佳机会validateObject()
,即使使用多行代码也是如此。如果没有找到该对象,那么我们可以适当地抛出异常InvalidObjectException
。原文:Java 中序列化的工作原理
GO TO FULL VERSION