Java中的序列化和反序列化
在本教程中,我们将了解序列化是如何工作的,以及我们如何实现它。我们还将看一些例子,说明我们如何使用各种Java概念来序列化一个数据结构。
Serialization 在Java中,序列化是一个实体的状态的字节流表示。所有实体的数据都在比特流中。 是序列化的反面,在这个过程中,字节数据类型流被转回一个内存对象。这两种技术最好的一点是它们都与JVM无关,这意味着你可以在一个JVM上进行序列化,在另一个JVM上进行去序列化。Deserialization
序列化的特点
- 机器独立。在一个单独的设备上,你可以反序列化任何封装的项目。
- 继承性。当一个父类是可序列化的,它的所有子类也是可序列化的。
为什么要进行序列化?
- 为了通过网络传输对象。
- 将Java对象保存在内存中。
- 在文件中保存Java对象。
序列化的好处
- 允许通过网络传输一个对象。
- 一个实体的状态被保存或持久化。
- 独立于JVM。
- 很容易理解和个性化。
在Java中利用序列化时,请记住以下几点。
Serializable接口中没有方法或数据成员。- 只使用Serializable接口来序列化一个对象。
- 一个类的字段必须全部是可序列化的;否则,请使用
transient关键字。 - 如果它的父类实现了Serializable接口,子类就不需要实现Serializable接口。
- 在序列化过程中,只保留非静态数据成员;静态和瞬时数据成员则不保留。
- String和它的封装类默认实现了Serializable接口。
如何在例子的帮助下进行序列化和反序列化
要进行序列化,请使用ObjectOutputStream 类的writeObject 方法。对于反序列化,使用InputObjectStream 类的readObject 方法。当在Java中把一个实体序列化到一个文件时,通常会使用.ser 这个扩展名。
writeObject的方法语法。
public final void writeObject(Object o) throws IOException
readObject的方法语法。
public final Object readObject() throws IOException, ClassNotFoundException
我们将使用我们完整的示例代码中的一些部分,这些部分将在后面显示。下面是完整代码的各个部分。
一个序列化的例子
考虑一下这个,Link 类,它包含两个数据成员:commons 和favorites 。这就是我们将如何定义它。
import java.io.*;
class Link implements Serializable {
private String commons;
protected int favorites;
public Link(String commons, int favorites) {
this.commons = commons;
this.favorites = favorites;
}
public void printLink() {
System.out.println("Link : " + this.commons);
}
}
为了序列化这个类,我们首先需要实现java.io.Serializable 接口,将其标记为可序列化。
class Link implements Serializable
然后我们实现以下方法。
public static void serializeLink(Link inputLink, String fileName) throws IOException {
FileOutputStream file = new FileOutputStream(fileName);
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(inputLink);
out.close();
file.close();
}
描述
这个函数将序列化一个作为参数提交的Link 对象。然后将序列化的字节写入一个文件中,我们将在其中指定名称作为第二个参数。通过out.writeObject(inputLink) 来处理序列化。通过调用file.close() ,我们终止了文件句柄。
反序列化的例子
现在可以通过使用下面列出的技术对序列化的对象进行反序列化。
public static Link deserializeLink(String fileName) throws IOException, ClassNotFoundException {
FileInputStream file = new FileInputStream(fileName);
ObjectInputStream on = new ObjectInputStream(file);
return (Link) on.readObject();
}
描述
deserializeLink() 方法有一个参数。参数名称是filename ,它是我们保存反序列化对象的位置。
- 使用这个程序,文件将以输入模式打开。
- 我们使用
in.readObject()进行反序列化。 - 之后,结果将被分类为一个链接对象。
如果该项目不存在,上述程序可能会抛出一个IOException 。当没有找到预期的类时,我们发出一个ClassNotFoundException 。
对象序列化和反序列化实例
下面是前面序列化和反序列化部分的完整代码。
序列化.java
import java.io.*;
class Link implements Serializable {
private String commons;
private int favorites;
public Link(String commons, int favorites) {
this.commons = commons;
this.favorites = favorites;
}
public void printLink() {
System.out.println("Link : " + this.commons);
}
}
public class Serialization {
public static void serializeLink(Link inputLink, String fileName) throws IOException {
FileOutputStream file = new FileOutputStream(fileName);
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(inputLink);
out.close();
file.close();
}
public static Link deserializeLink(String fileName) throws IOException, ClassNotFoundException {
FileInputStream file = new FileInputStream(fileName);
ObjectInputStream on = new ObjectInputStream(file);
return (Link) on.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Link randomLink = new Link("My first link", 5);
final String filename = "example.bin";
System.out.println("Prior to Serialization : ");
randomLink.printLink();
serializeLink(randomLink, filename);
Link linkFromFile = deserializeLink(filename);
System.out.println("Following Serialization : ");
linkFromFile.printLink();
}
}
输出
Before Serialization :
Link : My first link
After Serialization :
Link : My first link
如何在Java中使用继承来序列化一个对象
当我们把一个父类标记为可序列化时,它的所有子类都隐含地可序列化。因此,你不需要子类直接实现Serializable接口。
例子
import java.io.*;
class Link implements Serializable {
private String commons;
protected int favorites;
public Link(String commons, int favorites) {
this.commons = commons;
this.favorites = favorites;
}
public void printLink() {
System.out.println("Link : " + this.commons);
}
}
class SerialLink extends Link {
protected String commits;
public void addCommit(String newCommit)
{
if (this.commits == null) commits = "";
this.commits += (newCommit + ",");
}
public String getCommit() {
return this.commits;
}
public SerialLink(String commons, int favorites) {
super(commons, favorites);
}
}
说明
上面的例子中有两个类:Link 和SerialLink 。SerialLink 派生自Link ,有一个commits 数据成员,包含了所有留在链接上的提交。由于多级继承的概念,Link 实现了Serializable ,因此SerialLink 也将成为可序列化的。然而,这个想法并没有因此而反向操作。当一个子类实现了可序列化接口,父类不会受到影响。
带有聚合的序列化
为了在Java中建立一个HAS-A连接,我们利用聚合。这意味着一个单一的类可以引用许多不同的其他类。这些类不会被序列化,直到它们所有的引用都是Serializable 。由于NotSerializableException ,任何试图对这些类型进行序列化的尝试都会失败。
例子
class Link {
private String heading;
private String commons;
public String getlink() {
return this.commons + " : " + this.link;
}
}
class Seriallink {
private link link;
private int favorites;
}
说明
在上面的插图中,如果一个客户想要序列化SerialLink 对象,将会产生一个NotSerizalizableException 。因为在Serializable 接口被实现之前,link 不能被序列化,所以会发生这个错误。
当属性不被序列化时,有几件事情需要考虑。
- 静态成员
- 暂时性成员
用静态数据成员进行序列化
静态成员/变量属于一个类,不像实例成员/变量属于一个对象。重要的是,序列化只保留对象的状态,而不是类的静态组件的值。
由于静态成员属于它的类,而不是它的类的对象的一部分,当一个对象被序列化时,静态成员的值不会被存储。
暂时性成员
在某些情况下,客户端不希望一个实体的所有数据成员被序列化。对于这种使用场景,Java包括关键字transient 。在序列化的对象中,任何被声明为瞬时的数据成员将有一个默认的数据类型值。
例子
class Link implements Serializable {
public String commons;
public transient int commits;
public Link(String commons, int commits) {
this.commons = commons;
this.commits = commits;
}
public void printLink()
{
System.out.printf("Link : \"%s\" with %d commits.%n", this.commons, this.commits);
}
}
// Use the class as in our previous serialization example
输出
Before Serialization :
OurLink: "My first link" with 5 commons.
After Serialization :
OurLink: "My first link" with 0 commons.
描述
在序列化之后,注释的数量从5变成了null,也就是0。这是因为注释被标记为临时的,因此它们的值被设置为int数据类型的默认值0。
典型案例
有些变量可能有很大的数值,很难通过网络传达。因此,我们将这些数字归为临时性的。此外,如果客户不希望发布或保存一个变量的值,认为它太小或太个人化,我们可以将这些变量标记为暂时性的。
总结
在本教程中,我们已经讨论了序列化和反序列化。我们通过实例巩固了这些知识,在这些实例中我们可以应用明显的Java原则。在此过程中,我们了解了继承和聚合的OOP概念。最后,我们讨论了属性不被序列化的情况。