《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.2 理解对象序列化的文件格式

本文涉及的产品
.cn 域名,1个 12个月
简介: 本节书摘来华章计算机《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一书中的第2章 ,第2.4.2节,[美] 凯S.霍斯特曼(Cay S. Horstmann) 著陈昊鹏 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.4.2 理解对象序列化的文件格式

对象序列化是以特殊的文件格式存储对象数据的,当然,你不必了解文件中表示对象的确切字节序列,就可以使用writeObject/readObject方法。但是,我们发现研究这种数据格式对于洞察对象流化的处理过程非常有益。因为其细节显得有些专业,所以如果你对其实现不感兴趣,则可以跳过这一节。
每个文件都是以下面这两个字节的“魔幻数字”开始的
image

后面紧跟着对象序列化格式的版本号,目前是
image

(我们在本节中统一使用十六进制数字来表示字节。)然后,是它包含的对象序列,其顺序即它们存储的顺序。
字符串对象被存为
image

字符串中的Unicode字符被存储为修订过的UTF-8格式。
当存储一个对象时,这个对象所属的类也必须存储。这个类的描述包含

  • 类名。
  • 序列化的版本唯一的ID,它是数据域类型和方法签名的指纹。
  • 描述序列化方法的标志集。
  • 对数据域的描述。

指纹是通过对类、超类、接口、域类型和方法签名按照规范方式排序,然后将安全散列算法(SHA)应用于这些数据而获得的。
SHA是一种可以为较大的信息块提供指纹的快速算法,不论最初的数据块尺寸有多大,这种指纹总是20个字节的数据包。它是通过在数据上执行一个灵巧的位操作序列而创建的,这个序列在本质上可以百分之百地保证无论这些数据以何种方式发生变化,其指纹也都会跟着变化。(关于SHA的更多细节,可以查看一些参考资料,例如William Stallings所著的《Cryptography and Network Security: Principles and Practice》第7版[Prentice Hall, 2016]。)但是,序列化机制只使用了SHA码的前8个字节作为类的指纹。即便这样,当类的数据域或方法发生变化时,其指纹跟着变化的可能性还是非常大。
在读入一个对象时,会拿其指纹与它所属的类的当前指纹进行比对,如果它们不匹配,那么就说明这个类的定义在该对象被写出之后发生过变化,因此会产生一个异常。在实际情况下,类当然是会演化的,因此对于程序来说,读入较旧版本的对象可能是必需的。我们将在2.4.5节中讨论这个问题。
下面表示了类标识符是如何存储的:

  • 72
  • 2字节的类名长度
  • 类名
  • 8字节长的指纹
  • 1字节长的标志
  • 2字节长的数据域描述符的数量
  • 数据域描述符
  • 78(结束标记)
  • 超类类型(如果没有就是70)

标志字节是由在java.io.ObjectStreamConstants中定义的3位掩码构成的:
image

我们会在本章稍后讨论Externalizable接口。可外部化的类提供了定制的接管其实例域输出的读写方法。我们要写出的这些类实现了Serializable接口,并且其标志值为02,而可序列化的java.util.Date类定义了它自己的readObject/writeObject方法,并且其标志值为03。
每个数据域描述符的格式如下:

  • 1字节长的类型编码
  • 2字节长的域名长度
  • 域名
  • 类名(如果域是对象)

其中类型编码是下列取值之一:
image

当类型编码为L时,域名后面紧跟域的类型。类名和域名字符串不是以字符串编码74开头的,但域类型是。域类型使用的是与域名稍有不同的编码机制,即本地方法使用的
格式。
例如,Employee类的薪水域被编码为:
image

下面是Employee类完整的类描述符:
image
image

这些描述符相当长,如果在文件中再次需要相同的类描述符,可以使用一种缩写版:
image

这个序列号将引用到前面已经描述过的类描述符,我们稍后将讨论编号模式。
对象将被存储为:
image

例如,下面展示的就是Employee对象如何存储:
image

正如你所看见的,数据文件包含了足够的信息来恢复这个Employee对象。
数组总是被存储成下面的格式:
image

在类描述符中的数组类名的格式与本地方法中使用的格式相同(它与在其他的类描述符中的类名稍微有些差异)。在这种格式中,类名以L开头,以分号结束。
例如,3个Employee对象构成的数组写出时就像下面一样:
image

注意,Employee对象数组的指纹与Employee类自身的指纹并不相同。
所有对象(包含数组和字符串)和所有的类描述符在存储到输出文件时都被赋予了一个序列号,这个数字以00 7E 00 00开头。
我们已经看到过,任何给定的类其完整的类描述符只保存一次,后续的描述符将引用它。例如,在前面的示例中,对Date类的重复引用就被编码为:
image

相同的机制还被用于对象。如果要写出一个对之前存储过的对象的引用,那么这个引用也会以完全相同的方式存储,即71后面跟随序列号,从上下文中可以很清楚地了解这个特殊的序列引用表示的是类描述符还是对象。
最后,空引用被存储为:
image

下面是前面小节中ObjectRefTest程序的带注释的输出。如果你喜欢,可以运行这个程序,然后查看其数据文件employee.dat的十六进制码,并将其与注释列表比较。在输出中接近结束部分的几行重要编码展示了对之前存储过的对象的引用。
image
image

当然,研究这些编码大概与阅读常用的电话号码簿一样枯燥。了解确切的文件格式确实不那么重要(除非你试图通过修改数据来达到不可告人的目的),但是对象流对其所包含的所有对象都有详细描述,并且这些充足的细节可以用来重构对象和对象数组,因此了解它还是大有益处的。
你应该记住:

  • 对象流输出中包含所有对象的类型和数据域。
  • 每个对象都被赋予一个序列号。
  • 相同对象的重复出现将被存储为对这个对象的序列号的引用。
相关文章
|
12天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
36 2
|
4天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
8天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
33 17
|
2天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
3天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 5
|
4天前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
16 4
|
4天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
14 3
|
6天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
6天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
7天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。