揭开Java序列化的神秘面纱(下)Serializable源码剖析


在上一篇文章中我们明白了 Serializable 的大致用法。感兴趣的朋友,请前往查阅。揭开Java序列化的神秘面纱(上)Serializable使用详解。


本篇文章重点关注 Serializable 序列化的实现 ,一切从源头说起,Java序列化的设计和实现都源于Serializable这个看似简单的接口。作为Java序列化机制的基石,它的由来和精髓值得我们仔细探讨。


一、Serializable的起源和设计理念


`Serializable`接口是Java语言中用于支持对象序列化和反序列化的标准机制的一部分。序列化是将对象的状态信息转换为可以存储或传输的形式的过程,而反序列化则是这一过程的逆过程。

1、起源

序列化的概念并非Java所独有,它在计算机科学中有着悠久的历史,主要用于数据持久化和网络通信。Java在1995年由Sun Microsystems公司推出,而Serializable接口作为Java语言的一部分,也在这个时期被引入。

Serializable接口最初出现在JDK 1.1版本,其设计目的是为了支持对对象序列化的描述。

Java的序列化机制设计时考虑了以下几个方面:

  1. 跨平台性:Java语言的一个核心理念是“一次编写,到处运行”(Write Once, Run Anywhere,WORA)。序列化机制允许Java对象在不同的平台和Java虚拟机(JVM)之间进行传输,从而保持了跨平台性。

  2. 简单性:Java的序列化机制设计得非常简单直观。开发者只需通过实现Serializable接口,即可让一个类的对象支持序列化,无需编写额外的代码。

  3. 透明性:Java序列化是透明的,这意味着开发者不需要关心对象状态是如何被转换为字节序列的。Java运行时环境会自动处理这些细节。

  4. 兼容性:序列化机制需要能够处理类定义随时间变化的情况。Java通过serialVersionUID字段来确保序列化版本的兼容性。

  5. 安全性:Java序列化机制提供了一定的安全性,例如,通过transient关键字可以控制哪些字段被序列化,从而保护敏感信息。


2、设计理念


设计者们意识到在分布式环境和网络传输等场景下,将内存中的对象按照某种可复原的表示方式存储或传输是一种普遍的需求。于是Serializable接口应运而生。

作为标记接口,Serializable自身没有任何方法声明,所有的序列化细节都由JVM底层实现。

这一设计理念非常巧妙,它使得对象序列化的支持对于应用层来说是完全透明的,无需关心字节流的生成和解析细节。

对于对象的使用者来说,只需让目标类实现Serializable接口,JVM底层的序列化机制就会自动生效,从而达到对象层面的跨平台支持和数据迁移。

Serializable的设计理念考虑了以下方面:

  1. 对象状态的保存:序列化允许开发者保存对象的状态,这在需要持久化对象或在应用程序之间共享对象时非常有用。
  2. 网络传输:在分布式系统中,对象序列化可以用于通过网络传输对象,使得对象可以在不同的系统间移动。
  3. 简化开发:通过简化序列化和反序列化的过程,Java允许开发者更容易地构建需要这些功能的应用程序。
  4. 性能考虑:虽然Java序列化机制简单易用,但也考虑到了性能问题。例如,通过transient关键字可以排除不需要序列化的字段,减少序列化的数据量。
  5. 可扩展性:Java序列化机制允许开发者通过实现Externalizable接口或自定义writeObjectreadObject方法来扩展序列化过程,以满足特定的需求。
  6. 安全性:Java序列化在设计时也考虑了安全性,提供了机制来防止序列化过程中的某些攻击,如通过transient关键字忽略敏感字段。
  7. 版本控制:通过serialVersionUID,Java序列化机制支持类的版本控制,使得即使在类定义发生变化后,旧的序列化对象仍然可以被正确地反序列化。

总的来说,Java的Serializable接口和序列化机制的设计反映了Java语言的核心价值观:跨平台性、简单性、透明性和安全性。这些设计理念使得序列化成为了Java开发者工具箱中一个强大而灵活的工具。


二、深入剖析Serializable的源码


尽管Serializable只是一个空接口,但它所承载的序列化细节却相当丰富。我们从JDK的源码入手,探究这个简洁的接口背后的机制。

public interface Serializable {
}

在这里插入图片描述


看似简单,实则精深。实现Serializable接口的类,将自动获得对象序列化和反序列化的能力,底层序列化机制会对该类的实例进行编码,转换为一串字节序列。这一串字节序列可以持久化存储或通过网络传输。

  • Serializable 和 Externalizable 序列化接口。Serializable 接口没有方法或字段,仅用于标识可序列化的语义,实际上 ObjectOutputStream#writeObject 时通过反射调用 writeObject 方法,如果没有自定义则调用默认的序列化方法。Externalizable 接口该接口中定义了两个扩展的抽象方法:writeExternal 与 readExternal。
  • DataOutput 和 ObjectOutput DataOutput 提供了对 Java 基本类型 byte、short、int、long、float、double、char、boolean 八种基本类型,以及 String 的操作。ObjectOutput 则在 DataOutput 的基础上提供了对 Object 类型的操作,writeObject 最终还是调用 DataOutput 对基本类型的操作方法。
  • ObjectOutputStream 我们一般使用 ObjectOutputStream#writeObject 方法把一个对象进行持久化。ObjectInputStream#readObject 则从持久化存储中把对象读取出来。
  • ObjectStreamClass 和 ObjectStreamField ObjectStreamClass 是类的序列化描述符,包含类描述信息,字段的描述信息和 serialVersionUID。可以使用 lookup 方法找到/创建在此 Java VM 中加载的具体类的 ObjectStreamClass。而 ObjectStreamField 则保存字段的序列化描述符,包括字段名、字段值等。

ObjectOutputStream 源码分析

ObjectOutputStream是对象序列化的核心类,它的主要作用是将Java对象转换为字节序列并输出到底层的I/O流中。


1、主要功能
  • 对象写入ObjectOutputStream允许将对象写入输出流,包括对象的状态和对象之间的引用关系。
  • 引用处理:它能够处理对象之间的引用,确保每个对象只被写入一次,并且可以被正确地引用。
  • 序列化ObjectOutputStream负责调用对象的writeObject方法,或者如果对象实现了Externalizable接口,则调用writeExternal方法。
  • 异常处理:它能够处理序列化过程中可能发生的异常,如NotSerializableExceptionInvalidClassException
  • 协议版本控制ObjectOutputStream使用流协议版本号来确保序列化数据的兼容性。

2、数据结构
private final BlockDataOutputStream bout;   // io流
private final HandleTable handles;          // 序列化对象句柄(编号)映射关系
private final ReplaceTable subs;            // 替换对象的映射关系private final boolean enableOverride;       // true 则调用writeObjectOverride()来替代writeObject()
private boolean enableReplace;              // true 则调用replaceObject()
  • bout 是下层输出流,两个表是用于记录已输出对象的缓存。

  • handles ,HandleTable 从名称就知道这是一个轻量的 HashMap,保存序列化对象句柄(编号)映射关系。handles 的作用:Java 序列化除了保存字段信息外,还保存有类信息,当同一个对象序列化两次时第二次只用保存第一次的编号,这样可以大大减少序列化的大小。

  • subs,ReplaceTable 保存的是替换对象的映射关系。


3、 构造函数
public ObjectOutputStream(OutputStream out) throws IOException {bout = new BlockDataOutputStream(out);handles = new HandleTable(10(float) 3.00);subs = new ReplaceTable(10(float) 3.00);enableOverride = false;writeStreamHeader();bout.setBlockDataMode(true);
}
  • ObjectOutputStream 构建时会创建 BlockDataOutputStream 序列化流 bout,handles 和 subs 。

  • writeStreamHeader 方法是输出序列化流的头信息,用于文件校验,和 .class 文件头的魔数及版本作用一样。如果不需要,可以覆盖这个方法,什么也不做,Hadoop 默认的 Java 序列化就是这样。

protected void writeStreamHeader() throws IOException {bout.writeShort(STREAM_MAGIC);bout.writeShort(STREAM_VERSION);
}

4、核心方法
  • writeObject(Object obj)
public final void writeObject(Object obj) throws IOException{if (enableOverride) {writeObjectOverride(obj);return;}try {writeObject0(obj, false);} catch (IOException ex) {if (depths != null) {writeFatalException(ex);}throw ex;}
}

writeObject方法是序列化对象的主入口。它首先会检查是否需要通过writeObjectOverride方法进行自定义的序列化处理。如果没有自定义,则调用底层的writeObject0方法进行实际的序列化操作。


  • writeObject0(Object obj, boolean unshared)
private void writeObject0(Object obj, boolean unshared) throws IOException {...// 检查obj是否实现了Serializable接口if (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 {throw new NotSerializableException(cl.getName());}
}

writeObject0方法是真正执行序列化操作的地方。它首先会检查要序列化的对象的类型,如果是String、数组或Enum,会调用相应的序列化处理方法;如果是其他实现了Serializable接口的普通对象,则会调用writeOrdinaryObject方法进行处理。如果对象根本没有实现Serializable,则会抛出NotSerializableException异常。


  • writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared)
private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)throws IOException
{// 检查是否需要调用对象自身的writeReplace方法进行替换if (obj instanceof ObjectStreamClass.ClassDataSlime) {throw new IOException("Object graph corrupted");}if (desc.isProxy()) {// 处理代理类} else {// 调用底层的writeSerialData方法进行数据写入writeSerialData(obj, desc, unshared);}
}

对于普通的实现了Serializable接口的对象,writeOrdinaryObject方法会先检查对象是否需要通过writeReplace方法进行替换,如果需要则调用相应的替换逻辑。如果不需要替换,则调用writeSerialData方法来写入对象的实际序列化数据。


  • writeSerialData(Object obj,ObjectStreamClass desc, boolean unshared)
private void writeSerialData(Object obj,ObjectStreamClass desc,boolean unshared)throws IOException
{// 检查是否需要调用writeObject方法进行自定义序列化if (desc.isExternalizable() && !desc.isProxy()) {writeExternal(obj);} else {// 进行普通对象的序列化处理writeRegularObject(obj, desc, unshared);}
}

writeSerialData方法会检查要序列化的对象是否实现了Externalizable接口。如果实现了,就会调用对应的writeExternal方法进行自定义序列化。否则,会调用writeRegularObject方法进行普通对象的序列化处理。

通过上面几个核心方法的分析,我们可以看出ObjectOutputStream在序列化Java对象时,主要做了以下几件事:

  • 检查要序列化的对象是否实现了Serializable或Externalizable接口,如果没有实现任何一个,则抛出NotSerializableException异常。

  • 检查对象是否实现了writeReplace等钩子方法,如果实现了则优先进行替换逻辑。

  • 检查对象是否实现了Externalizable接口,如果实现了则调用自定义的writeExternal方法进行序列化。

  • 对于普通的实现了Serializable接口的对象,会调用底层的writeRegularObject等方法进行真实的序列化操作,将对象的字段数据转换为字节流并写入底层的输出流中。

总的来说,ObjectOutputStream的实现充分体现了Java对象序列化机制的基本流程和原理。它会根据对象的具体类型和实现的序列化接口,采取不同的序列化策略,并最终将对象转换为能够跨平台和网络传输的字节序列流。这种动态分发的实现使得Java序列化机制的功能相对完整,并且还预留了自定义序列化的扩展点。


三、Serializable接口的局限性


尽管Serializable接口的设计理念优雅,为序列化编程提供了便利,但它也暴露出了一些局限性:

  1. 全盘序列化
    实现Serializable后,对象中的所有非transient字段都会被序列化,无法进行按需序列化。这对于包含大量不需序列化数据的对象来说是一种浪费。
  2. 版本控制问题
    当类定义发生变化时,JVM只能基于serialVersionUID进行过时的版本控制。面对类定义的较大变动,仍有可能导致反序列化异常。
  3. 扩展性差
    Serializable接口的功能是编码在底层JVM中,缺乏扩展能力。对于一些更加复杂的序列化需求(例如跨语言),就显得力有未逮。
  4. 安全性隐患
    由于序列化机制的透明性,一些重要的敏感字段也有可能被序列化,存在潜在的安全隐患。

四、Externalizable的更好方案


面对Serializable接口的局限性,Java还提供了另一个更加强大的序列化接口Externalizable。通过显式地实现writeExternal和readExternal方法,开发者可以自主决定序列化的字节表示形式,甚至将自定义序列化操作独立为第三方库。

public class Person implements Externalizable {public void writeExternal(ObjectOutput out) throws IOException {// 自定义序列化逻辑}public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {// 自定义反序列化逻辑 }...
}

Externalizable提供了更大的灵活性和扩展性,是实现对象序列化和网络传输的更佳选择。当然,它也需要开发者编写更多的代码。在一定程度上,它平衡了简单性和可控性之间的取舍。


五、Externalizable 接口 与 Serializable接口对比


Externalizable接口相比Serializable接口有以下一些主要优势和不足:

优势:

  1. 可控性强
    Externalizable接口需要显式实现writeExternal和readExternal方法,开发者可以完全自主控制序列化和反序列化的过程和字节表示形式。这使得开发者能够根据实际需求对序列化行为进行定制,比如只序列化某些字段、加密敏感数据等。
  2. 更小的序列化体积
    由于可以自主选择序列化哪些字段,使用Externalizable通常可以获得比Serializable更小的序列化后字节流大小,这在对序列化体积要求较高的场景(如网络传输)中很有优势。
  3. 版本控制更优雅
    Externalizable机制本身并不直接提供版本控制,但开发者可以在自定义的序列化/反序列化逻辑中实现自己的版本控制策略,相比Serializable版本控制的局限性更加灵活。
  4. 支持跨语言
    理论上,Externalizable的序列化数据可以使用任意字节编码格式,因此也就支持了跨语言的对象序列化,而Serializable是java语言内部使用的编码格式。

不足:

  1. 编码工作量大
    使用Externalizable需要为每个类手动实现writeExternal和readExternal方法,编码工作量较大。而Serializable是自动完成序列化的,只需标记接口即可。
  2. 无法自动绑定引用
    对于对象图的序列化,Externalizable无法像Serializable那样自动绑定对象间的引用关系,需要自行维护这些关系。
  3. 无法利用默认序列化
    Externalizable完全由开发者自己实现序列化逻辑,无法调用JVM提供的默认序列化行为,对一些简单场景来说有些复杂了。
  4. IDE支持较差
    主流IDE对Externalizable的支持较差,没有为其提供代码生成等便利性,而对于Serializable则通常都有良好的支持。

总的来说,Externalizable提供了比Serializable更大的灵活性和可控性,但代价是牺牲了一定的易用性和自动化程度。在对象序列化需求复杂或有特殊要求的场合,使用Externalizable会是更好的选择;而如果只是简单的序列化需求,使用Serializable就已经足够了。两者有自己适用的领域,需要根据实际情况进行选型。


总之,Serializable虽然简单,却蕴含了Java序列化机制的精华。透过这个曾经平凡的接口,我们可以更好地理解序列化在Java编程中的重要地位,同时也体会到它作为底层机制的不足之处。或许在未来,Java序列化机制会有进一步的革新,但Serializable这一点点设计独特之处,将持续影响着Java大家庭。


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

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

相关文章

ue5 后期处理体积lut如何加入

零、需要颜色查找表格&#xff0c;ps 一、ps中 把调节好的shift 一起拖入颜色查找表格 二、存储为png格式 另存为 保护好原来的颜色查找表格 三、导入ue5中 四、在后期处理体积中搜索misc 替换颜色查找表格 五、双击后期处理体积 纹理组替换颜色查找表格 2. 压缩设置lut改成…

Java常见集合类三(Map,HashMap put 方法详解)

一、Map 接口概述 Map 中存放键值对的 key 是唯一的&#xff0c;value 是可以重复的&#xff1b; Map 中的 key 可以全部分离出来&#xff0c;存储到 Set 中来进行访问 (因为 key 不能重复)&#xff1b; Map 中的 value 也可以全部分离出来&#xff0c;存储在 Collection 的任…

中文金融大语言模型:专业、智能、全面,一站式金融解决方案,你的专属LLM金融顾问

中文金融大语言模型&#xff1a;专业、智能、全面&#xff0c;一站式金融解决方案&#xff0c;你的专属LLM金融顾问 DISC-FinLLM 是一个专门针对金融场景下为用户提供专业、智能、全面的金融咨询服务的金融领域大模型&#xff0c; 资料下载&#xff1a;https://download.csdn.…

TTime:截图翻译/OCR

日常网页翻译Translate Web Pages完全足够&#xff0c;TTime最重要的功能&#xff0c;还是截图翻译&#xff0c;还有个厉害的功能&#xff0c;就是静默OCR&#xff0c;相比之前的分享的识字精灵效率更高。 软件使用 打开软件&#xff0c;点击翻译源设置&#xff0c;建议勾选一…

第十三期Big Demo Day亮点项目:CCarbon重塑碳交易生态,助力全球绿色发展

第十三期Big Demo Day活动即将于2024年5月28日在香港数码港的CyberArena隆重举行。我们荣幸地宣布&#xff0c;利用区块链技术优化全球碳交易CCarbon项目将亮相&#xff0c;参与精彩的项目路演。本次活动由ZeeprLabs、BiKing Exchange、Gather冠名赞助&#xff0c;Central Rese…

DOS学习-目录与文件应用操作经典案例-ren

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 案例 1&#xff1a;重命名当前目录下的文件 案例 2&#xff1a…

超清高帧,成像升级 | SWIR短波红外相机500万像素992芯片

博图光电5MP短波红外相机&#xff0c;搭载了索尼IMX992 SenSWIR传感器&#xff0c;支持5.2MP分辨率&#xff0c;适合探测波长在400nm-1700nm波段的可见光和短波红外光&#xff0c;有效面积和透光率得到提升&#xff0c;内置TEC制冷片&#xff0c;实现了像素尺寸和图像均匀性方面…

网络实时安全:构筑数字时代的铜墙铁壁

什么是网络实时安全&#xff1f; 网络实时安全&#xff0c;简而言之&#xff0c;是一种能够在威胁发生的瞬间即刻识别、响应并有效抵御的安全机制。它强调的是速度与效率&#xff0c;确保网络环境能够持续处于安全状态。这背后&#xff0c;离不开高科技的支撑——扩展检测系统…

Linux源码编译安装MySQL + Qt连接MySQL

一、准备工作 1. 编译环境&#xff1a; 银河麒麟V10 飞腾D2000 CPU 2. 下载MySQL源码 这里编译的是5.7.44版本&#xff0c;带Boost库&#xff0c;这是官网的下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 3. 解压压缩包 tar -zxvf mys…

Python内置方法串讲:类型转化与实用技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、类型转化&#xff1a;从A到B的魔法 二、实用技巧&#xff1a;避免类型错误 三、总结 一…

Datawhale团队第十一期录取名单!

Datawhale团队 公示&#xff1a;Datawhale团队成员 Datawhale成立五年了&#xff0c;从一开始的12个人&#xff0c;学习互助&#xff0c;到提议成立开源组织&#xff0c;做更多开源的事情&#xff0c;帮助更多学习者&#xff0c;也促使我们更好地成长。于是有了我们的使命&…

一年前的Java作业,模拟游戏玩家战斗

说明&#xff1a;一年前写的作业&#xff0c;感觉挺有意思的&#xff0c;将源码分享给大家。 刚开始看题也觉得很难&#xff0c;不过写着写着思路更加清晰&#xff0c;发现也没有想象中的那么难。 一、作业题目描述&#xff1a; 题目&#xff1a;模拟游戏玩家战斗 1.1 基础功…

假象和谎言

原创 | 刘教链 隔夜BTC&#xff08;比特币&#xff09;徘徊在69k一线。5.25教链内参报告&#xff0c;《BTC ETF持仓即将超越中本聪》。ETH ETF的尘嚣逐渐散去&#xff0c;复归于平静。戏刚唱了个开头&#xff0c;结尾还留着悬念。4000刀之于ETH看来是个关键阻力位&#xff0c;最…

element ui 的密码输入框点击显示隐藏密码时,图标随之改变

场景图&#xff1a; 原理&#xff1a; 通过修改el-input框的type属性&#xff0c;来设置显示或者隐藏。从而改变图标地址。 <el-input class"passwordinput" :type"pwdObj.pwdType" ref"pwdInput" placeholder"密码"v-model"…

Vue热更新出现内存溢出

Vue热更新出现内存溢出 vue-cli2遇到此问题的解决办法&#xff1a;vue-cli3遇到此问题的解决办法&#xff1a;方法一&#xff08;已测试ok&#xff09;方法二&#xff08;未尝试&#xff09; 开发项目有一段时间了&#xff0c;随着项目越来越大&#xff0c;打包的时间也相应的变…

若依微服务实现分布式事务

一、基本介绍 1、什么是分布式事务 指一次大的操作由不同的小操作组成的&#xff0c;这些小的操作分布在不同的服务器上&#xff0c;分布式事务需要保证这些小操作要么全部成功&#xff0c;要么全部失败。从本质上来说&#xff0c;分布式事务就是为了保证不同数据库的数据一致…

ssm137基于SSM框架的微博系统+vue

微博系统网站的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就…

家用洗地机哪个品牌好?家用洗地机排行榜前十名

随着洗地机逐渐进入大众视野&#xff0c;这种集吸、拖、洗功能于一体的清洁工具&#xff0c;凭借其高效便捷的特点&#xff0c;成为家庭清洁的新宠。洗地机不仅能够减少地面清洁时间&#xff0c;节省体力&#xff0c;还能提高清洁效果。然而&#xff0c;面对琳琅满目的洗地机品…

Web会话管理

一、会话管理的概念&#xff1a; 在人机交互时&#xff0c;会话管理是保持用户的整个会话活动的互动与计算机系统跟踪过程会话管理分类: 桌面会话管理、浏览器会话管理、Web服务器的会话管理。 二、为什么需要会话管理&#xff1f; HTTP是一种无状态协议&#xff0c;一次请…

java项目之智能家居系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的智能家居系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于Springboot的智能家居系…