Java 序列化与反序列化的原理

在Java中,序列化(Serialization)是将对象转换为字节流的过程,而反序列化(Deserialization)是将字节流转换回对象的过程。这种机制允许对象在网络上传输或在磁盘上持久化存储。

序列化的原理

标记接口

Java中的java.io.Serializable接口是一个标记接口,用于指示类的对象可以被序列化。只要类实现了这个接口,Java序列化系统就能够对该类的对象进行序列化。

标记接口是一个不包含任何方法或属性的接口,其作用主要是为了在运行时标记某个类是否具有特定的能力或特征。Java中的java.io.Serializable接口就是一个典型的标记接口。当一个类实现了Serializable接口时,它的对象就可以被序列化。

编译器检查

Java编译器在编译时会检查是否有Serializable接口的实现。如果一个类实现了Serializable接口,编译器会为该类加上一个标记,表明这个类的对象可以被序列化。

Java序列化系统

Java序列化系统在序列化对象时,会检查对象所属的类是否实现了Serializable接口。如果实现了这个接口,Java序列化系统会允许对该类的对象进行序列化;否则,会抛出NotSerializableException异常。

序列化方法

Serializable接口内部并没有定义任何方法,它仅仅是一个标记,告诉Java序列化系统该类可以被序列化。Java序列化系统会使用默认的序列化方法来序列化对象,这些方法会将对象的状态写入到输出流中,以便后续的反序列化。

对象的持久化

通过实现Serializable接口,对象的状态可以被持久化到磁盘或通过网络进行传输。在反序列化时,Java序列化系统会根据序列化时写入的字节流,重新构建对象,并将对象的状态恢复到序列化时的状态。

总之,java.io.Serializable接口作为一个标记接口,标志着一个类的对象可以被序列化。Java序列化系统在序列化和反序列化过程中会根据这个标记来确定是否允许对对象进行序列化和反序列化。

当一个类实现了Serializable接口,它的对象就可以被序列化。下面是一个简单的示例:

import java.io.*;// 实现Serializable接口的类
class MyClass implements Serializable {private static final long serialVersionUID = 1L; // 序列化版本号private int id;private String name;// 构造方法public MyClass(int id, String name) {this.id = id;this.name = name;}// Getter和Setter方法public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}public class SerializationExample {public static void main(String[] args) {// 创建一个MyClass对象MyClass obj = new MyClass(1, "Example");// 将对象序列化到文件try {FileOutputStream fileOut = new FileOutputStream("object.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(obj);out.close();fileOut.close();System.out.println("对象已被序列化到 object.ser 文件");} catch (IOException e) {e.printStackTrace();}// 从文件中反序列化对象try {FileInputStream fileIn = new FileInputStream("object.ser");ObjectInputStream in = new ObjectInputStream(fileIn);MyClass newObj = (MyClass) in.readObject();in.close();fileIn.close();System.out.println("从 object.ser 文件反序列化的对象:");System.out.println("ID: " + newObj.getId());System.out.println("Name: " + newObj.getName());} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}

在上面的示例中:

  • MyClass类实现了Serializable接口。
  • SerializationExample类包含了一个main方法,其中创建了一个MyClass对象,并将其序列化到文件object.ser中。
  • 然后,从文件中读取对象,并反序列化为新的MyClass对象,并输出其ID和名称。

通过实现Serializable接口,MyClass对象可以被成功序列化和反序列化。

writeObjectreadObject方法是Java中用于自定义序列化和反序列化过程的方法,它们不是直接的序列化和反序列化方法,但是在序列化和反序列化过程中起着非常重要的作用。

writeObject方法
  • writeObject方法是在对象序列化时被调用的方法。当一个类实现了Serializable接口,并且其中包含了writeObject方法时,Java序列化系统在序列化该类的对象时会调用这个方法,而不是默认的序列化机制。
  • 开发者可以在writeObject方法中自定义对象序列化的过程,可以手动控制哪些字段需要被序列化,如何处理特定的字段,以及进行一些额外的操作。
  • writeObject方法的签名为private void writeObject(ObjectOutputStream out) throws IOException,参数是一个ObjectOutputStream对象,开发者可以使用这个对象来将对象的状态写入到输出流中。
readObject方法
  • readObject方法是在对象反序列化时被调用的方法。当一个类实现了Serializable接口,并且其中包含了readObject方法时,Java反序列化系统在反序列化该类的对象时会调用这个方法,而不是默认的反序列化机制。
  • 开发者可以在readObject方法中自定义对象反序列化的过程,可以手动控制如何从输入流中读取对象的状态,并重新构建对象。
  • readObject方法的签名为private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException,参数是一个ObjectInputStream对象,
    开发者可以使用这个对象从输入流中读取对象的状态。

通过自定义writeObjectreadObject方法,开发者可以实现对对象序列化和反序列化过程的精细控制,从而适应特定的需求和场景。

对象转换为字节流

当需要序列化对象时,Java运行时系统会将对象转换为字节流。这个字节流包含了对象的数据以及一些额外的信息,比如对象的类名、类的结构等等。

序列化后的字节流包含了对象的数据以及一些额外的信息,这些信息是为了在反序列化时能够正确地恢复对象的状态。字节流的结构可以大致分为以下几个部分

魔数(Magic Number)

序列化后的字节流的开头通常包含一个魔数,用于标识这个字节流是一个Java序列化流。魔数是固定的值,用于确保字节流的有效性和可靠性。

版本号(Version Number)

字节流中可能会包含一个版本号,用于标识序列化时所使用的Java版本。这个版本号可以帮助在反序列化时选择正确的反序列化算法以及处理特定版本的兼容性问题。

类描述信息(Class Descriptor)

序列化后的字节流会包含对象所属类的描述信息,这个描述信息包括了对象所属类的名称、字段的类型和名称等信息。这些信息用于在反序列化时重建对象所属类的结构。

对象数据(Object Data)

字节流中的主要部分是对象的数据,即对象的各个字段的值。这些数据按照字段的顺序依次排列,每个字段的值都被转换为字节流并按照特定的格式编码。

其他辅助信息

除了上述信息外,序列化后的字节流可能还包含一些其他辅助信息,比如对象的超类信息、对象的引用信息等。这些信息有助于在反序列化时正确地重建对象的状态。

总结一下,序列化后的字节流包含了对象的数据以及一些额外的信息,这些信息用于在反序列化时准确地重建对象的状态。字节流的结构是由Java序列化系统定义的,并且在不同的Java版本中可能会有所不同。

持久化或传输

序列化后的字节流可以被写入到磁盘上,用于持久化存储。也可以通过网络传输到远程机器上。

反序列化的原理

在Java中,反序列化的过程是根据读取到的字节流以及相关的额外信息,通过Java运行时系统重新构建对象,并将对象的状态恢复到序列化时的状态。下面是反序列化过程的详细描述

准备对象流

在反序列化过程开始之前,需要准备一个对象输入流(ObjectInputStream),用于从字节流中读取对象的数据。

读取字节流

Java运行时系统从字节流中读取数据,并根据数据的结构逐步重建对象。读取的数据可能包括魔数、版本号、类描述信息、对象数据等。

解析类描述信息

Java运行时系统首先解析字节流中的类描述信息,这些信息包括了对象所属类的名称、字段的类型和名称等。根据这些信息,系统可以确定对象所属的类以及类的结构。

加载类

如果对象所属类的类文件尚未加载到内存中,Java运行时系统会根据类描述信息动态加载类,并创建类的模板对象。这个过程是通过类加载器完成的。

创建对象

根据类的模板对象,Java运行时系统创建一个新的对象,并为对象分配内存空间。

恢复对象状态

根据字节流中的对象数据,Java运行时系统将对象的状态恢复到序列化时的状态。系统会按照序列化时的顺序逐个读取对象的字段值,并将这些值设置到新创建的对象中。

对象图的重建

如果序列化的对象包含了其他对象的引用,Java运行时系统会根据引用信息递归地重建对象图。这个过程是通过递归调用反序列化方法完成的。

对象图指的是对象之间的引用关系。在序列化和反序列化过程中,如果序列化的对象包含了其他对象的引用,那么这些引用关系在对象图中被保留。对象图描述了对象之间的关联关系,它是一个由对象及其之间引用关系组成的图形结构。

在反序列化过程中,Java运行时系统会根据对象的引用信息递归地重建对象图。这意味着当一个对象被反序列化时,如果它包含了其他对象的引用,Java运行时系统会首先尝试反序列化这些引用指向的对象,并在对象图中建立相应的关联关系。这个过程是通过递归调用反序列化方法来完成的,确保整个对象图能够正确地重建出来。

总之,对象图描述了对象之间的引用关系,在反序列化过程中,Java运行时系统会根据引用信息递归地重建对象图,确保对象之间的关联关系得以恢复。

当一个对象在反序列化过程中具有循环依赖时,也就是说对象之间存在相互引用关系,Java运行时系统在构建对象图时需要采取特殊处理,以避免无限递归或者栈溢出等问题。
通常情况下,Java运行时系统在构建对象图时会采取以下策略之一

延迟初始化

Java运行时系统在遇到循环引用时,可以选择延迟初始化对象之间的引用关系。即在第一次遇到循环引用时,先不完全构建对象图,而是留下一个占位符或者空引用,等到后续处理时再填充这些引用。这种延迟初始化的方式可以避免无限递归,同时在后续处理时逐步完善对象之间的关联关系。

特殊处理循环引用

Java运行时系统可以特殊处理循环引用的情况,例如通过记录已经反序列化的对象,以及对象之间的引用关系,避免重复构建相同的对象,从而防止无限递归。这种方式需要对对象图的构建过程进行细致的管理和控制,以确保循环引用能够正确地处理。

自定义处理策略

在某些情况下,开发者可以通过自定义反序列化过程来处理循环引用。例如,可以在反序列化方法中手动管理循环引用的解析顺序,或者采用特定的数据结构来辅助循环引用的处理。这种方式需要开发者对反序列化过程有深入的理解,并具备一定的编程能力。

总结一下,当一个对象在反序列化过程中具有循环依赖时,Java运行时系统会采取一些特殊的策略来构建对象图,以确保对象之间的关联关系能够正确地建立,并避免出现无限递归或者栈溢出等问题。

初始化对象

在Java中,在对象状态恢复完成后,Java运行时系统不会调用对象的构造函数。事实上,在反序列化过程中,并不会调用任何构造函数来创建对象。相反,Java运行时系统会直接恢复对象的状态,而不会通过构造函数来初始化对象。

对于反序列化过程中对象的初始化,主要依赖于两个方面

反序列化过程中恢复对象的状态

在反序列化过程中,Java运行时系统会根据序列化的字节流和类的描述信息来重新构建对象,并将对象的状态恢复到序列化时的状态。这个过程不涉及调用对象的构造函数,而是直接将对象的字段值设置为序列化时保存的值。

执行特定的逻辑(如果需要)

如果需要在反序列化过程中执行一些额外的初始化操作,可以通过自定义readObject方法来实现。在readObject方法中,可以对反序列化后的对象进行进一步的处理,包括执行额外的初始化逻辑等。但是需要注意的是,readObject方法必须是private修饰,并且方法签名必须是private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException。在readObject方法中,也不会调用对象的构造函数。

关于对象没有公共的构造函数的问题,对象可以被反序列化。即使对象没有公共的构造函数,Java运行时系统也可以通过其他途径来创建对象实例,并将对象的状态恢复到序列化时的状态。反序列化过程中不需要调用对象的构造函数,因为对象的状态是通过反序列化的字节流来恢复的,而不是通过构造函数来初始化的。

返回对象

最后,Java运行时系统返回重新构建的对象,完成了反序列化过程。开发者可以使用返回的对象进行后续的操作。

总结一下,反序列化过程是一个复杂的过程,涉及了从字节流中读取数据、解析类描述信息、动态加载类、创建对象、恢复对象状态等多个步骤。通过这个过程,Java运行时系统可以准确地重建序列化时的对象,并将对象的状态恢复到序列化时的状态。

序列化的注意事项

版本兼容性

在进行序列化时,需要注意类的版本兼容性,即确保序列化的对象在不同版本间能够正确地反序列化。

安全性

序列化和反序列化过程中可能存在安全风险,比如对象注入(Object Injection)和反序列化漏洞(Deserialization Vulnerabilities)。因此,在处理不受信任的数据时,需要谨慎使用序列化和反序列化功能。

序列化ID

序列化ID(Serialization ID)是用来唯一标识序列化类的字段,它在序列化和反序列化过程中起着重要的作用。序列化ID的作用包括

版本控制

序列化ID用于标识类的版本。如果在反序列化时,类的序列化ID与当前类的序列化ID不匹配,Java运行时系统会抛出InvalidClassException异常,这有助于检测到类的版本不一致的情况,从而进行版本控制。

兼容性

通过显式指定序列化ID,可以保证在类的结构发生变化时依然能够向后兼容。例如,在添加新字段或修改字段类型时,可以保持序列化ID不变,这样在反序列化时仍然能够正确地恢复对象的状态。

防止反序列化漏洞

序列化ID也用于防止反序列化漏洞,因为一些恶意攻击者可能会尝试通过修改类的结构或者序列化ID来执行攻击。通过显式指定序列化ID,可以增加攻击者对类结构的改变的难度。

提高性能

序列化ID可以加速反序列化过程。当序列化ID存在并匹配时,Java运行时系统可以直接进行反序列化,而不必进行额外的版本检查和适配工作。

总结一下,序列化ID在序列化和反序列化过程中扮演着重要的角色,它用于版本控制、兼容性保证、防止反序列化漏洞以及提高性能等方面。通过显式指定序列化ID,可以确保对象在不同版本间的正确序列化和反序列化,并提高系统的安全性和可靠性。

transient关键字

transient是Java中的关键字,用于修饰类的成员变量。当一个成员变量被transient修饰时,它表示该变量不会参与对象的序列化过程,即在对象被序列化时,该变量的值不会被保存到字节流中,而在对象被反序列化时,该变量会被赋予默认值。

对象的安全性和隐私保护

有些类的成员变量可能包含敏感信息,例如密码、密钥等。通过将这些变量声明为transient,可以防止它们在对象序列化时被写入到磁盘或通过网络传输,从而提高了对象的安全性和隐私保护。

序列化灵活性

有些类的成员变量并不需要被序列化,例如临时计算结果、缓存数据等。通过将这些变量声明为transient,可以减少序列化时所需的存储空间,并提高序列化和反序列化的效率。
下面是一个示例,演示了transient关键字的用法

import java.io.*;
class MyClass implements Serializable {private static final long serialVersionUID = 1L;private String name;private transient int age; // 声明为 transient,不会被序列化public MyClass(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}
}
public class TransientExample {public static void main(String[] args) {MyClass obj = new MyClass("John", 30);try {// 将对象序列化到文件FileOutputStream fileOut = new FileOutputStream("object.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(obj);out.close();fileOut.close();System.out.println("对象已被序列化到 object.ser 文件");// 从文件中反序列化对象FileInputStream fileIn = new FileInputStream("object.ser");ObjectInputStream in = new ObjectInputStream(fileIn);MyClass newObj = (MyClass) in.readObject();in.close();fileIn.close();System.out.println("从 object.ser 文件反序列化的对象
");System.out.println("Name
" + newObj.getName()); // 输出 Name
JohnSystem.out.println("Age
" + newObj.getAge());   // 输出 Age
0(默认值,因为 age 字段是 transient} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}

在上面的示例中,age字段被声明为transient,因此在对象被序列化时,age字段的值不会被保存到文件中。在反序列化时,age字段会被赋予默认值,即0

总结一下,Java中序列化和反序列化的原理是通过将对象转换为字节流并将其写入到磁盘或传输到网络上,然后再从字节流中读取数据并重建对象。这种机制为Java提供了灵活的数据持久化和传输的方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/698940.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用nginx正向代理https网站

目录 1. 缘起2. 部署nginx3. 测试3.1 http测试3.2 https测试4 给centos设置代理访问外网 1. 缘起 最近碰到了一个麻烦事情,就是公司的centos测试服务器放在内网环境,而且不能直接上外网,导致无法通过yum安装软件,非常捉急。   幸…

QPaint绘制自定义仪表盘组件02

网上视频抄的&#xff0c;用来自己看一下&#xff0c;看完就删掉 最终效果 ui&#xff0c;创建一个空的widget widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPainter> #include <QTimer>QT_BEGIN_NAMESPACE namespace Ui { c…

Java学习笔记2024/2/23

今日内容 多态 包 final 权限修饰符 代码块 教学目标 能够说出使用多态的前提条件理解多态的向上转型理解多态的向下转型能够知道多态的使用场景包的作用public和private权限修饰符的作用描述final修饰的类的特点描述final修饰的方法的特点描述final修饰的变量的特点 第…

Android Studio六大基本布局详解

在Android Studio中&#xff0c;六大基本布局用于构建用户界面的结构和外观。这些布局类型提供了不同的方式来组织和排列界面元素&#xff0c;以创建出各种复杂和灵活的界面设计。以下是这六大基本布局的详解&#xff1a; LinearLayout&#xff08;线性布局&#xff09;&#x…

k8s-配置与存储-配置管理

文章目录 一、配置存储1.1 ConfigMap1.1.1.基于文件夹的创建方式1.1.2指定文件的创建方式1.1.3 配置文件创建configmap 1.2 Secret1.2.1Secret的应用与Docker仓库 Secret设置1. Kubernetes 中的 Secrets&#xff1a;创建 Secret 示例&#xff1a;将 Secret 挂载到 Pod 中的示例…

Python爬虫-报错requests.exceptions.SSLError: HTTPSConnectionPool

在学习python爬虫&#xff0c;在公司运行代码没有问题&#xff0c;但是下班回来把代码拉下来运行&#xff0c;却出现问题。 问题&#xff1a; requests.exceptions.SSLError: HTTPSConnectionPool(host‘campusgateway.51job.com’, port443): Max retries exceeded with url…

Flashbit空投

空投要点 明牌空投交互简单&#xff0c;仅需3步&#xff0c;零gas费要求加密钱包在eth链有过交易需要有x和discord账号 空投简介 是一个社区驱动的项目&#xff0c;专门针对Blast生态&#xff0c;项目方提出了空投计划&#xff0c;参与过该生态其他项目空投的都清楚&#xf…

【Delphi 基础知识 35】MainMenu控件的详细使用

把TmenuMain放在Form后&#xff0c;右击控件就可以对菜单进行设计 菜单中添加分割线只需加“-”就可以添加一个分割线 级联菜单的设计 单击鼠标右键弹出菜单中选择Create Submenu菜单项 单选功能设计 要在设计的菜单项目中选择RadioItem属性为True&#xff0c;Checked属性…

Git介绍与使用

Git介绍与常用命令的使用 目录: 一、Git简介 二、Git简单命令行入门 三、Git常用命令 四、常见问题补充 一、Git简介 Git 是一个开源的分布式版本控制系统&#xff0c;是目前世界上最先进、最流行的版本控制系统。可以快速高效地处理从很小到非常大的项目版本管理。特点&…

Chat With RTX 安装、使用问题记录

1.安装包运行检测环境失败 安装适合的的CUDA&#xff1a;https://developer.nvidia.com/cuda-downloads?target_osWindows&target_archx86_64&target_version11 2.安装Chat With RTX 和 模型 Mistral 7B 失败 科学上网&#xff0c;可以单独装Chat With RTX 先&…

Windows+Yolo3-darknet训练自己数据集并测试

WindowsYolo3-darknet训练自己的数据集并测试 一、首要条件 Windows 7下配置好VS2015OPENCV3.4.2YOLO3CUDA10.0CUDNN7.5生成darknet.exe。具体配置可参考我的博客&#xff1a;https://blog.csdn.net/wszswllnzn_/article/details/100760477 二.制作数据集 1、方法1 使用软件la…

ASPxGridView中使用PopupEditForm表单字段联动填充

c#中devexpress的控件ASPxGridView中使用PopupEditForm表单字段联动填充 //选择项目名称&#xff0c;自动填充项目编号 <Columns><dx:GridViewDataTextColumn FieldName"id" ReadOnly"True" VisibleIndex"0" Visible"False"…

com.alibaba.nacos.api.exception.NacosException: Request nacos server failed

问题描述 安装nacos2.0以上版本&#xff0c;启动报错:com.alibaba.nacos.api.exception.NacosException: Request nacos server failed com.alibaba.nacos.api.exception.NacosException: Request nacos server failed: at com.alibaba.nacos.client.naming.remote.gprc.Nami…

MFC 多文档程序的基本编程

下载了一个openGL mfc的多文档程序,以此来学习mfc多文档模式的编程; 1 基本编程 它每次新建一个文档,会在窗口绘制一个三角形、一个矩形;如果没有了图形刷新一下; 先看一下为什么每次打开新文档会绘制图形; 生成工程之后主要有5个类,比单文档程序多了一个子框架类; 可…

记录一下我的Ruby On Rails的systemd服务脚本

自己也是一个 ROR 框架的学习者&#xff0c;同时也是 Ruby 的新手。对于如何让 ROR 应用随系统自动启动并不是很了解。在尝试了各种方法之后&#xff0c;我最终找到了一条可行的途径。虽然不确定是否完全正确&#xff0c;但服务已经成功启动了。因此&#xff0c;我决定在这里保…

hive中如何取交集并集和差集

交集 要获取两个表的交集&#xff0c;你可以使用INNER JOIN或者JOIN&#xff1a; SELECT * FROM table1 JOIN table2 ON table1.column_name table2.column_name;也可以使用 INTERSECT 关键字 SELECT * FROM table1 INTERSECT SELECT * FROM table2;并集 要获取两个表的并集…

华为HCIP Datacom H12-831 卷23

单选题 1、某园区部署IS-IS实现网络互通&#xff0c;在所有IS-IS路由器的进程中配置命令flash-flood 6 max-timer-interval 100 Leve1-2&#xff0c;则以下关于该场景的描述,正确的是哪—项? A、若某IS-IS路由器LSDB内更新的LSP数量为5,则在100毫秒内且路由计算完成前&#…

Java向ES库中插入数据报错:I/O reactor status: STOPPED

Java向ES库中插入数据报错&#xff1a;java.lang.IllegalStateException: Request cannot be executed; I/O reactor status: STO 一、问题问题原因 二、解决思路 一、问题 在使用Java向ES库中插入数据时&#xff0c;第一次成功插入&#xff0c;第二次出现以下错误&#xff1a…

【力扣经典面试题】238. 除自身以外数组的乘积

目录 一、题目描述 二、题解分析 思路&#xff1a; 算法步骤&#xff1a; 代码(C版)&#xff1a; 三、总结 一、题目描述 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证…

【八股文面试】Java基础常见面试题总结(上)

Java基础常见面试题总结(上) Java有哪些特性 简单易学&#xff1b;面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff1b;平台无关性&#xff08; Java 虚拟机实现平台无关性&#xff09;&#xff1b;支持多线程&#xff08; C 语言没有内置的多…