Java中的序列化和反序列化

115 阅读7分钟

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 类,它包含两个数据成员:commonsfavorites 。这就是我们将如何定义它。

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 ,它是我们保存反序列化对象的位置。

  1. 使用这个程序,文件将以输入模式打开。
  2. 我们使用in.readObject() 进行反序列化。
  3. 之后,结果将被分类为一个链接对象。

如果该项目不存在,上述程序可能会抛出一个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);
    }
}

说明

上面的例子中有两个类:LinkSerialLinkSerialLink 派生自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 不能被序列化,所以会发生这个错误。

当属性不被序列化时,有几件事情需要考虑。

  1. 静态成员
  2. 暂时性成员

用静态数据成员进行序列化

静态成员/变量属于一个类,不像实例成员/变量属于一个对象。重要的是,序列化只保留对象的状态,而不是类的静态组件的值。

由于静态成员属于它的类,而不是它的类的对象的一部分,当一个对象被序列化时,静态成员的值不会被存储。

暂时性成员

在某些情况下,客户端不希望一个实体的所有数据成员被序列化。对于这种使用场景,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概念。最后,我们讨论了属性不被序列化的情况。