Java序列化
WHAT 什么是序列化
序列化(Serialization):指将对象转换成可传输格式的过程,以便在需要时可以将其还原成原始对象。
在Java中,对象序列化将对象转换为一系列字节流,这些字节流可以写入文件或通过网络传输,并且可以在需要时重新创建相同的对象。
WHY 为什么java要进行序列化
java中进行序列化可以使java对象在网上传输或者在不同进程之间进行传输,可以将java对象保存到磁盘上以便之后使用,可以提高应用程序的性能,还可以用于实现分布式计算。
-
远程调用(RPC)Remote Procedure Call,在分布式系统中,远程调用(RPC)是一种常见的通信方式,通过序列化Java对象可以将方法调用参数和返回值在不同的进程之间传递,使得分布式系统的开发更加方便。
-
持久化:在Java中,可以使用对象序列化将Java对象保存到磁盘上,以便之后的使用。这样做的好处是可以减少对象的初始化时间,并且可以避免频繁地从数据库或其他存储介质中读取数据。
-
缓存:在某些场景下,将Java对象序列化并存储在缓存中可以提高应用程序的性能,因为对象在缓存中可以更快地被加载和使用。
-
分布式计算:在分布式计算中,Java对象序列化是实现数据共享和数据传输的一种有效方式。
那除了使用序列化的方式让java对象在网络传输以及持久化,那还有其他方式吗?
- 使用JSON或XML等数据格式:将Java对象转换为JSON或XML格式的数据,以便于进行网络传输或持久化存储。这种方式相对于序列化来说更灵活,可以在不同的语言之间进行数据交互,但是也需要一定的转换成本。
- 数据库存储:将 Java 对象的数据存储在关系型或非关系型数据库中,可以使用 JPA、Hibernate、MyBatis 等 ORM 框架将 Java 对象映射为数据库表,然后进行持久化。。
本质都是将该格式转成二进制流进行传输、存储。注意二进制是一种数据技术方式,计算机中所以的数据都是以二进制形式进行存储,而二进制流是计算机中以二进制形式流动的数据,二进制流是指多个字节组成的数据流,包括文本、图像等各种类型的数据,它们在计算机中都是以二进制流的形式进行存储。
WHEN 一般什么情况下需要进行序列化
- 对象需要在网络上传输:因为在网络上传输数据时,数据必须以二进制形式进行传输,而java对象通常是以内存中的对象形式存在的,因此需要将java对象序列化为字节流后才能进行网络传输。
- 对象需要在不同的进程或者计算机之间传递:因为不同的进程或计算机之间无法直接共享内存,java对象在不同系统中可能具有不同的内存布局,需要将java对象序列化为字节流后才能在不同的进程或计算机之间进行传递。
- 对象需要持久化存储时:因为将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对象中的一些属性和方法不想进行序列化有哪些方式。
- 被static修饰的字段和方法不会被序列化
- 被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序列化有更全面的理解。