WHAT 什么是序列化

序列化(Serialization):指将对象转换成可传输格式的过程,以便在需要时可以将其还原成原始对象。

在Java中,对象序列化将对象转换为一系列字节流,这些字节流可以写入文件或通过网络传输,并且可以在需要时重新创建相同的对象。

WHY 为什么java要进行序列化

java中进行序列化可以使java对象在网上传输或者在不同进程之间进行传输,可以将java对象保存到磁盘上以便之后使用,可以提高应用程序的性能,还可以用于实现分布式计算。

  1. 远程调用(RPC)Remote Procedure Call,在分布式系统中,远程调用(RPC)是一种常见的通信方式,通过序列化Java对象可以将方法调用参数和返回值在不同的进程之间传递,使得分布式系统的开发更加方便。

  2. 持久化:在Java中,可以使用对象序列化将Java对象保存到磁盘上,以便之后的使用。这样做的好处是可以减少对象的初始化时间,并且可以避免频繁地从数据库或其他存储介质中读取数据。

  3. 缓存:在某些场景下,将Java对象序列化并存储在缓存中可以提高应用程序的性能,因为对象在缓存中可以更快地被加载和使用。

  4. 分布式计算:在分布式计算中,Java对象序列化是实现数据共享和数据传输的一种有效方式。

那除了使用序列化的方式让java对象在网络传输以及持久化,那还有其他方式吗?

  1. 使用JSON或XML等数据格式:将Java对象转换为JSON或XML格式的数据,以便于进行网络传输或持久化存储。这种方式相对于序列化来说更灵活,可以在不同的语言之间进行数据交互,但是也需要一定的转换成本。
  2. 数据库存储:将 Java 对象的数据存储在关系型或非关系型数据库中,可以使用 JPA、Hibernate、MyBatis 等 ORM 框架将 Java 对象映射为数据库表,然后进行持久化。。

本质都是将该格式转成二进制流进行传输、存储。注意二进制是一种数据技术方式,计算机中所以的数据都是以二进制形式进行存储,而二进制流是计算机中以二进制形式流动的数据,二进制流是指多个字节组成的数据流,包括文本、图像等各种类型的数据,它们在计算机中都是以二进制流的形式进行存储。

WHEN 一般什么情况下需要进行序列化

  1. 对象需要在网络上传输:因为在网络上传输数据时,数据必须以二进制形式进行传输,而java对象通常是以内存中的对象形式存在的,因此需要将java对象序列化为字节流后才能进行网络传输。
  2. 对象需要在不同的进程或者计算机之间传递:因为不同的进程或计算机之间无法直接共享内存,java对象在不同系统中可能具有不同的内存布局,需要将java对象序列化为字节流后才能在不同的进程或计算机之间进行传递。
  3. 对象需要持久化存储时:因为将Java对象保存到磁盘上,必须将Java对象序列化为字节流之后才能进行存储。

补充:字节流与二进制是什么关系?

我们都知道在计算机中,所有数据都是以二进制形式存储的,因此字节流本质上就是二进制流。
在Java中字节流可以看做是二进制流的抽象,它是一组用于读写二进制数据的API,可以通过它来读取、写入二进制数据,包括文件等。

字节流在Java中的实现类有两种:InputStream和OutputStream,可以用来读写字节流。通常我们会使用字节流来读取和写入二进制数据。
例如:音频文件、视频文件、图片等等。而在将Java对象进行序列化时,也是将java对象转换为字节流的形式,以便在网络上传输或者持久化存储。

因此字节流本质上就是对二进制数据的抽象。通过使用字节流,我们可以方便地读写二进制数据,并且可以将Java对象序列化为字节流进行传输或者存储。

HOW 如何进行序列化

Java实现序列化有两种方式:实现 Serializable 接口实现 Externalizable 接口

实现 Serializable 接口是比较常见的方式,它是 Java 提供的一个简单易用的序列化机制。通过实现 Serializable 接口,Java 对象的序列化和反序列化过程会被自动处理,无需进行手动实现。这种方式比较简单,适合对序列化过程不需要过多控制的场景。

而实现 Externalizable 接口需要手动实现对象的序列化和反序列化过程,包括 writeExternal() 和 readExternal() 两个方法。这种方式相对于实现 Serializable 接口,提供了更多的控制,可以更加灵活地处理对象的序列化和反序列化过程,但需要更多的代码实现。适合对序列化过程有一定控制需求的场景。

实现Serializable 接口的序列化和反序列化代码示例

业务场景是将java对象通过实现Serializable进行序列化将java对象存储在磁盘中。再通过反序列化将磁盘中的数据创建成java对象。

import java.io.*;

public class SerializeExample  implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public SerializeExample(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * @description:进行序列化
     * @author: wangwei
     * @date: 2023/4/15 9:13
     * @param: []
     * @return: void
     **/
    public void serialize() {
        try {
            //创建生成example.ser文件的路劲
            File file = new File("src\\main\\java","example.ser");
            //创建一个FileOutputStream对象制定将数据写入到example.sex文件中。这个过程称为对象的持久化。
            FileOutputStream fileOut = new FileOutputStream(file);
            //创建了一个ObjectOutputStream对象实现将Java对象序列化后写入到文件中
            ObjectOutputStream out = new ObjectOutputStream(fileOut);

            /*
            *第1步将当前对象序列化并写入输出流中,第2步和第3步是关闭输出流和文件输出流,
            * 以确保对象已经被完全写入文件中。如果不关闭输出流和文件输出流,那么对象可能没有被完全写入文件中,
            * 或者文件可能处于不一致的状态。因此,关闭输出流和文件输出流是一个良好的编程习惯,
            * 可以避免一些潜在的问题。
            */
            //将当前对象序列化并写入输出流中
            out.writeObject(this);
            //关闭输出流
            out.close();
            //文件输出流
            fileOut.close();
            System.out.println("Serialized data is saved in example.ser");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void deserialize() throws IOException, ClassNotFoundException {
        File file = new File("src\\main\\java","example.ser");
        // 反序列化
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        SerializeExample p = (SerializeExample)ois.readObject();
        ois.close();
        fis.close();
        System.out.println(p.name); // Alice
        System.out.println(p.age); // 20
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SerializeExample example = new SerializeExample("John", 30);
        example.serialize();
        example.deserialize();
    }
}

实现效果

Java序列化-LMLPHP

如果对于java对象中的一些属性和方法不想进行序列化有哪些方式。

  1. 被static修饰的字段和方法不会被序列化
  2. 被transient修饰的字段和方法不会被序列化

被 static 修饰的字段和方法不会被序列化,因为它们是属于类的而不是属于对象的。在序列化的过程中,Java 会将对象的状态序列化,而静态字段和方法是属于类的,不会保存在对象的状态中,因此不会被序列化。在反序列化的过程中,静态字段和方法也不会被还原。

被transient修饰的字段和方法在序列化的过程中会被忽略,从而不会被进行序列化。

通过这两种方式,如何我们不想java对象中的某些比较隐私相关的字段不被序列化,这里不做演示,感兴趣的可以进行实操。

实现Serializable的类中为什么需要private static final long serialVersionUID = 1L?

在实现Serializable接口的Java类中,需要声明一个私有静态常量serialVersionUID,该值是用来确定对象序列化版本的唯一标识符。它可以保证在序列化和反序列化时,不同的类版本之间不会发生不兼容性的问题。

如果不显式地声明serialVersionUID,在类结构被修改后,重新编译后的类的serialVersionUID可能会发生改变,从而导致反序列化时出现InvalidClassException异常。

serialVersionUID的值有什么要求吗?

serialVersionUID是通过一个64位的哈希算法来计算的,因此它的值是一个long型的数字。serialVersionUID的值没有特定的要求,只需要保证每个序列化版本的唯一标识符是不同的即可。通常情况下,建议将serialVersionUID设置为一个固定的值,以便在反序列化时可以正确地匹配版本。

显示声明了serialVersionUID后,如果类的实例字段被更改或添加或删除,serialVersionUID仍然不会发生改变。

serialVersionUID的作用是标识序列化对象的版本,用于在反序列化时验证版本的一致性,因此如果类的实例字段被更改或添加或删除,但是序列化对象的版本并未改变,则在反序列化时仍然可以成功。但是如果类的实例字段被更改或添加或删除,而序列化对象的版本已经改变,那么在反序列化时就会抛出InvalidClassException异常。因此,为了避免出现这种情况,建议在类中显式声明serialVersionUID,并且不要轻易更改它的值。

总结

博主整理和总结的关于Java序列化的知识还是很片面,算是一个阶段性总结,关于Java序列化还需要对计算机网络、远程调用、ORM等等理解更深入才能对Java序列化有更全面的理解。

04-15 14:48