Java 序列化机制详解

Java 序列化机制是一种将对象转换为字节流的过程,以便在网络上传输或保存到文件中,并能在需要时将字节流还原为对象。这一机制通过实现 java.io.Serializable 接口来实现,同时涉及到一些关键概念和注意事项。

java-serial.jpg

Serializable 接口

Serializable 接口是 Java 提供的标记接口,没有包含任何需要实现的方法。实现了这个接口的类表明其对象是可序列化的,可以被转换为字节流。

public interface Serializable {
}

通过实现 Serializable 接口,标识类的对象可以被序列化。这使得对象可以在网络上传输或保存到文件中,而不失去其状态和结构。

序列化过程

序列化是将对象的状态(字段值)转换为字节流的过程。这个过程由 ObjectOutputStream 类来完成。序列化使得对象可以以字节流的形式进行存储或传输,便于在不同系统之间进行数据交换。如下我们列举几个重要的方法的源码:

  • writeObject方法

public final void writeObject(Object obj) throws IOException {if (enableOverride) {writeObjectOverride(obj);return;}try {writeObject0(obj, false);} catch (IOException ex) {if (depth == 0) {writeFatalException(ex);}throw ex;}
}

enableOverride 表示是否启用了对象写入的覆盖机制。如果启用,会调用 writeObjectOverride 方法来执行对象的特定写入逻辑。

如果没有启用覆盖机制,则调用 writeObject0 方法执行实际的对象序列化。

writeObject0 方法负责处理对象的序列化,其中第二个参数 false 表示不使用不共享的方式进行序列化。

如果在序列化过程中抛出 IOException 异常,会捕获该异常。如果当前深度为0(表示不在嵌套序列化过程中),则调用 writeFatalException 方法来处理异常,否则将异常重新抛出。

  • writeObject0

private void writeObject0(Object obj, boolean unshared)throws IOException
{boolean oldMode = bout.setBlockDataMode(false);depth++;try {// handle previously written and non-replaceable objectsint h;if ((obj = subs.lookup(obj)) == null) {writeNull();return;} else if (!unshared && (h = handles.lookup(obj)) != -1) {writeHandle(h);return;} else if (obj instanceof Class) {writeClass((Class) obj, unshared);return;} else if (obj instanceof ObjectStreamClass) {writeClassDesc((ObjectStreamClass) obj, unshared);return;}// check for replacement objectObject orig = obj;Class<?> cl = obj.getClass();ObjectStreamClass desc;for (;;) {// REMIND: skip this check for strings/arrays?Class<?> repCl;desc = ObjectStreamClass.lookup(cl, true);if (!desc.hasWriteReplaceMethod() ||(obj = desc.invokeWriteReplace(obj)) == null ||(repCl = obj.getClass()) == cl){break;}cl = repCl;}if (enableReplace) {Object rep = replaceObject(obj);if (rep != obj && rep != null) {cl = rep.getClass();desc = ObjectStreamClass.lookup(cl, true);}obj = rep;}// if object replaced, run through original checks a second timeif (obj != orig) {subs.assign(orig, obj);if (obj == null) {writeNull();return;} else if (!unshared && (h = handles.lookup(obj)) != -1) {writeHandle(h);return;} else if (obj instanceof Class) {writeClass((Class) obj, unshared);return;} else if (obj instanceof ObjectStreamClass) {writeClassDesc((ObjectStreamClass) obj, unshared);return;}}// remaining casesif (obj instanceof String) {writeString((String) obj, unshared);} else if (cl.isArray()) {writeArray(obj, desc, unshared);} else if (obj instanceof Enum) {writeEnum((Enum<?>) obj, desc, unshared);} else if (obj instanceof Serializable) {writeOrdinaryObject(obj, desc, unshared);} else {if (extendedDebugInfo) {throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString());} else {throw new NotSerializableException(cl.getName());}}} finally {depth--;bout.setBlockDataMode(oldMode);}
}

反序列化过程

当需要从字节流中恢复对象时,Java 序列化机制会将字节流还原为对象的状态。这个过程由 ObjectInputStream 类来完成。如下我们列举几个重要的方法的源码:

  • readObject()

private final Object readObject(Class<?> type)throws IOException, ClassNotFoundException
{if (enableOverride) {return readObjectOverride();}if (! (type == Object.class || type == String.class))throw new AssertionError("internal error");// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(type, false);handles.markDependency(outerHandle, passHandle);ClassNotFoundException ex = handles.lookupException(passHandle);if (ex != null) {throw ex;}if (depth == 0) {vlist.doCallbacks();}return obj;} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}}
}

这段代码的主要作用是根据给定的类型 (type) 进行对象的反序列化。在这个过程中,它使用了一些状态变量,如 enableOverride、passHandle、handles、depth、vlist 等,来管理反序列化的过程。在处理嵌套对象时,它通过 markDependency 方法标记了当前对象与封闭对象的依赖关系。在深度为 0 时,执行了clear方法。具体反序列化执行的核心方法是readObject0()

  • readObject0()

private Object readObject0(Class<?> type, boolean unshared) throws IOException {boolean oldMode = bin.getBlockDataMode();if (oldMode) {int remain = bin.currentBlockRemaining();if (remain > 0) {throw new OptionalDataException(remain);} else if (defaultDataEnd) {/** Fix for 4360508: stream is currently at the end of a field* value block written via default serialization; since there* is no terminating TC_ENDBLOCKDATA tag, simulate* end-of-custom-data behavior explicitly.*/throw new OptionalDataException(true);}bin.setBlockDataMode(false);}byte tc;while ((tc = bin.peekByte()) == TC_RESET) {bin.readByte();handleReset();}depth++;totalObjectRefs++;try {switch (tc) {case TC_NULL:return readNull();case TC_REFERENCE:// check the type of the existing objectreturn type.cast(readHandle(unshared));case TC_CLASS:if (type == String.class) {throw new ClassCastException("Cannot cast a class to java.lang.String");}return readClass(unshared);case TC_CLASSDESC:case TC_PROXYCLASSDESC:if (type == String.class) {throw new ClassCastException("Cannot cast a class to java.lang.String");}return readClassDesc(unshared);case TC_STRING:case TC_LONGSTRING:return checkResolve(readString(unshared));case TC_ARRAY:if (type == String.class) {throw new ClassCastException("Cannot cast an array to java.lang.String");}return checkResolve(readArray(unshared));case TC_ENUM:if (type == String.class) {throw new ClassCastException("Cannot cast an enum to java.lang.String");}return checkResolve(readEnum(unshared));case TC_OBJECT:if (type == String.class) {throw new ClassCastException("Cannot cast an object to java.lang.String");}return checkResolve(readOrdinaryObject(unshared));case TC_EXCEPTION:if (type == String.class) {throw new ClassCastException("Cannot cast an exception to java.lang.String");}IOException ex = readFatalException();throw new WriteAbortedException("writing aborted", ex);case TC_BLOCKDATA:case TC_BLOCKDATALONG:if (oldMode) {bin.setBlockDataMode(true);bin.peek();             // force header readthrow new OptionalDataException(bin.currentBlockRemaining());} else {throw new StreamCorruptedException("unexpected block data");}case TC_ENDBLOCKDATA:if (oldMode) {throw new OptionalDataException(true);} else {throw new StreamCorruptedException("unexpected end of block data");}default:throw new StreamCorruptedException(String.format("invalid type code: %02X", tc));}} finally {depth--;bin.setBlockDataMode(oldMode);}
}

serialVersionUID

serialVersionUID 是用于版本控制的序列化版本号。它是一个长整型数值,用于标识类的版本。通过显式声明 serialVersionUID,可以在类结构发生变化时依然能够正确地进行反序列化。

@Data
public class LoginUserInfo implements Serializable {private static final long serialVersionUID = 1L;...
}

如果在类中没有明确声明 serialVersionUID,Java 运行时系统会根据类的结构自动生成一个。这种自动生成的 serialVersionUID 是基于类的各个方面的,包括字段、方法、父类等。如果类的结构发生变化,可能导致自动生成的 serialVersionUID 发生变化。这可能会导致在反序列化时,类的版本不一致,从而导致 InvalidClassException 异常。

所以显式声明 serialVersionUID是确保反序列化过程正确的关键,避免因类结构变化而导致的问题。

transient 关键字

关键字 transient 用于标记字段,表示在对象序列化的过程中,这个字段应该被忽略。例如,如果一个类有一个不希望被序列化的缓存字段,可以使用 transient 关键字来避免将其写入序列化数据。例如ArrayList、LinkedList 等类中的一些属性就是使用transient修饰的:

_20231218215928.jpg

_20231218215951.jpg

自定义序列化和反序列化

有时候,可能需要自定义序列化和反序列化的过程以满足特定需求。可以通过实现 writeObject 和 readObject 方法来实现自定义逻辑。如ArrayList类中就是通过自定义的序列化和反序列化方法:

private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();// Write out size as capacity for behavioural compatibility with clone()s.writeInt(size);// Write out all elements in the proper order.for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}if (modCount != expectedModCount) {throw new ConcurrentModificationException();}
}/*** Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,* deserialize it).*/
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {elementData = EMPTY_ELEMENTDATA;// Read in size, and any hidden stuffs.defaultReadObject();// Read in capacitys.readInt(); // ignoredif (size > 0) {// be like clone(), allocate array based upon size not capacityint capacity = calculateCapacity(elementData, size);SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);ensureCapacityInternal(size);Object[] a = elementData;// Read in all elements in the proper order.for (int i=0; i<size; i++) {a[i] = s.readObject();}}
}

序列化不保存静态变量

  • 对象状态 vs. 类状态

序列化的主要目的是保存对象的状态,即对象的实例变量。静态变量是类级别的,它们对于每个对象实例都是相同的。序列化关注的是对象的实例状态,因为这是对象在不同环境中重建时所需的关键信息。

  • 节省空间

静态变量通常用于表示类级别的常量或共享数据,这些数据在所有对象实例之间是相同的。如果每个对象的静态变量都被序列化并存储,将导致冗余,浪费存储空间。序列化的目标之一是尽可能紧凑地保存对象的状态,因此不保存静态变量是一种优化。

  • 不需要还原

静态变量在类加载时初始化,并在整个应用程序的生命周期内保持不变。因此,在反序列化时不需要重新初始化静态变量。序列化和反序列化的目标是保存和还原对象的动态状态,而不是类级别的静态状态。

序列化的安全性和性能考虑

在实际应用中,需要注意序列化的安全性和性能。反序列化过程中可能存在安全风险,因此要谨慎处理来自不受信任源的序列化数据。此外,对于大量数据的序列化,可能会影响系统性能,可以考虑使用更高效的序列化工具或压缩算法。

总结

综合来看,Java 序列化的核心思想是将对象的状态转换为字节流,并通过 ObjectOutputStream 类完成这一过程。该类在内部处理了对象引用的记录、对象字段的写入、自定义写入方法的执行等。在实际应用中,我们需要注意序列化版本控制、对象字段的 transient 关键字的处理以及序列化性能等方面的问题。

请注意,Java 序列化机制在现代应用中可能会遇到一些挑战,包括性能问题、安全性问题以及与其他语言的兼容性等。因此,在一些场景下,开发者可能会考虑使用其他序列化框架,如 JSON 或 Protocol Buffers,以满足不同的需求。

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

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

相关文章

开发知识点-09Rust

Rust Rust 语言通常用于编写系统级软件、网络服务器和高性能应用程序&#xff0c;它具有以下特点&#xff1a;1. 高性能和内存安全&#xff1a;Rust 在保证高性能的同时&#xff0c;利用其所有权模型和借用检查器等特性确保内存安全&#xff0c;避免了 C/C 等语言的内存错误和崩…

瑞友天翼应用虚拟化系统 多处SQL 注入漏洞复现(可RCE)

0x01 产品简介 瑞友天翼应用虚拟化系统是西安瑞友信息技术资讯有限公司研发的具有自主知识产权,基于服务器计算架构的应用虚拟化平台。它将用户各种应用软件集中部署在瑞友天翼服务器(群)上,客户端通过WEB即可快速安全的访问经服务器上授权的应用软件,实现集中应用、远程接…

Pytorch nn.Linear()的基本用法与原理详解及全连接层简介

主要引用参考&#xff1a; https://blog.csdn.net/zhaohongfei_358/article/details/122797190 https://blog.csdn.net/weixin_43135178/article/details/118735850 nn.Linear的基本定义 nn.Linear定义一个神经网络的线性层&#xff0c;方法签名如下&#xff1a; torch.nn.Li…

生日蜡烛C语言

分析&#xff1a;假设这个人只能活到100岁&#xff0c;如果不这样规定的话&#xff0c;那么这个人就可以假设活到老236岁&#xff0c;直接一次吹236个蜡烛&#xff0c;我们就枚举出所以情况&#xff0c;从一岁开始。 #include <stdio.h> int f(int a,int b){//计算从a到…

视频素材网站全新上线,海量高清视频等你来探索~

亲爱的视频制作爱好者们&#xff0c;好消息来啦&#xff01;我们的视频素材网站全新上线啦&#xff01;这次我们为大家带来了海量的高清视频素材&#xff0c;无论是风景、城市、人物、动物还是各种特效、背景等&#xff0c;应有尽有&#xff0c;满足您在视频制作过程中的各种需…

【神器】wakatime代码时间追踪工具

文章目录 wakatime简介支持的IDE安装步骤API文档插件费用写在最后 wakatime简介 wakatime就是一个IDE插件&#xff0c;一个代码时间追踪工具。可自动获取码编码时长和度量指标&#xff0c;以产生很多的coding图形报表。这些指标图形可以为开发者统计coding信息&#xff0c;比如…

【MySQL】:复合查询

复合查询 一.多表查询二.自连接三.子查询1.单行子查询2.多行子查询3.多列子查询4.在from语句里使用子查询5.合并查询 准备三张表 emp表 dept表 salgrade表 一.多表查询 实际开发中往往数据来自不同的表&#xff0c;所以需要多表查询。我们用一个简单的公司管理系统&#xff0c…

HPM6750系列--第十一篇 Uart讲解(轮询模式)

一、目的 在介绍完GPIO的相关内容下一个必须介绍的就是uart了&#xff0c;因为串口一个主要用途就是用于调试信息打印。 HPM6750在uart的配置上也是相当炸裂&#xff0c;有17个串口&#xff1b;结合HPM6750的高主频高内存&#xff0c;完全可以作为一个串口服务器。 ​​​​​​…

智能优化算法应用:基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.天牛须算法4.实验参数设定5.算法结果6.参考文…

MLOps在极狐GitLab 的现状和前瞻

什么是 MLOps 首先我们可以这么定义机器学习&#xff08;Machine Learning&#xff09;&#xff1a;通过一组工具和算法&#xff0c;从给定数据集中提取信息以进行具有一定程度不确定性的预测&#xff0c;借助于这些预测增强用户体验或推动内部决策。 同一般的软件研发流程比…

【lesson17】MySQL表的基本操作--表去重、聚合函数和group by

文章目录 MySQL表的基本操作介绍插入结果查询&#xff08;表去重&#xff09;建表插入数据操作 聚合函数建表插入数据操作 group by&#xff08;分组&#xff09;建表插入数据操作 MySQL表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#x…

【TB作品】STM32 PWM之实现呼吸灯,STM32F103RCT6,晨启

文章目录 完整工程参考资料实验过程 实验任务&#xff1a; 1&#xff1a;实现PWM呼吸灯&#xff0c;定时器产生PWM&#xff0c;控制实验板上的LED灯亮灭&#xff1b; 2&#xff1a;通过任意两个按键切换PWM呼吸灯输出到两个不同的LED灯&#xff0c;实现亮灭效果&#xff1b; 3&…

Axure的案例演示

增删改查&#xff1a; 在中继器里面展示照片

创建型模式之抽象工厂模式

一、概述 1、抽象工厂模式&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 2、抽象工厂模式&#xff1a;一个工厂可以生产一系列产品&#xff08;一族产品&#xff09;&#xff0c;极大减少了工厂类的数量 3、抽象工厂模式&am…

众和策略:加强经济监测预测预警 加大宏观调控力度

12月17日至18日&#xff0c;全国展开和革新作业会议在京举行&#xff0c;整理总结2023年展开革新作业&#xff0c;组织布置2024年展开革新关键使命。会议指出&#xff0c;中心经济作业会议对本年经济作业作了全面体系总结&#xff0c;侧重我国经济全体上升向好&#xff0c;全年…

选择合适教育管理软件:必须考虑的10个关键问题

随着教育行业的迅速数字化&#xff0c;学校要能够提供最新的管理和教育方法。大家逐渐意识到技术让运营变得更容易、更有效率。 不过首先我们需要找到一个能满足需求的应用程序。面对众多的选择&#xff0c;你该如何选择一个合适的平台呢&#xff1f;当然&#xff0c;没有人想…

MYSQL中使用IN,在xml文件中怎么写?

MYSQL&#xff1a; Spring中&#xff1a; mysql中IN后边的集合&#xff0c;在后端中使用集合代替&#xff0c;其他的没有什么注意的&#xff0c;还需要了解foreach 语法即可。

Spark编程实验一:Spark和Hadoop的安装使用

目录 一、目的与要求 二、实验内容 三、实验步骤 1、安装Hadoop和Spark 2、HDFS常用操作 3、Spark读取文件系统的数据 四、结果分析与实验体会 一、目的与要求 1、掌握在Linux虚拟机中安装Hadoop和Spark的方法&#xff1b; 2、熟悉HDFS的基本使用方法&#xff1b; 3、掌…

SCADA助力食品加工数字化变革:未来产业的智慧引擎

一、背景介绍 当前&#xff0c;在国际市场竞争加剧、消费者个性化需求突出的背景下&#xff0c;我国食品加工行业面临着诸多挑战&#xff1a;越发严苛的食品安全标准、追求供应链的透明度和效率、进一步提高产品质量和降低成本等等。 为了应对上述挑战&#xff0c;我国食品加…

亚马逊,速卖通,shein卖家如何准确有效的测评补单

一、合理规划测评时间和数量 卖家需要合理规划测评的时间和数量。如果卖家过于频繁地进行测评&#xff0c;或者在短时间内进行大量的测评&#xff0c;这可能会被视为恶意行为&#xff0c;从而触犯风控机制。因此&#xff0c;卖家需要根据自己的销售情况和市场需求&#xff0c;…