Marshal 格式

The Marshal 格式用于序列化 Ruby 对象。该格式可以通过三种用户定义的扩展机制存储任意对象。

有关使用 Marshal 序列化和反序列化对象的文档,请参阅 Marshal 模块。

本文档将一组序列化后的对象称为流。Ruby 实现可以从 StringIO 或实现 getc 方法的对象中加载一组对象。

流格式

流的前两个字节包含主版本号和次版本号,每个字节都用一个字节编码一个数字。Ruby 中实现的版本是 4.8(存储为“x04x08”),并由 ruby 1.8.0 及更高版本支持。

不同主版本的 Marshal 格式不兼容,无法被其他主版本理解。较小的次版本格式可以被较新的次版本理解。格式 4.7 可以被 4.8 实现加载,但格式 4.8 无法被 4.7 实现加载。

在版本字节之后是一个描述序列化对象的流。该流包含嵌套对象(与 Ruby 对象相同),但流中的对象不一定与 Ruby 对象模型直接映射。

流中的每个对象都由一个字节描述其类型,后面跟着一个或多个字节描述该对象。当下面提到“对象”时,它指的是下面定义 Ruby 对象的任何类型。

true、false、nil

这些对象每个都是一个字节长。“T” 代表 true,“F” 代表 false,“0” 代表 nil

Fixnum 和 long

“i” 代表一个使用打包格式的带符号 32 位值。类型后面跟着 1 到 5 个字节。加载的值始终为 Fixnum。在 32 位平台(Fixnum 的精度小于 32 位)上,加载大值会导致 CRuby 溢出。

fixnum 类型用于表示 Ruby Fixnum 对象和序列化数组、哈希、实例变量和其他类型的尺寸。在以下部分中,“long” 将表示下面描述的格式,它支持完整的 32 位精度。

第一个字节具有以下特殊值

“x00”

整数的值为 0。后面没有字节。

“x01”

整数的总大小为两个字节。后面的字节是 0 到 255 范围内的正整数。只有 123 到 255 之间的数值应该以这种方式表示,以节省字节。

“xff”

整数的总大小为两个字节。后面的字节是 -1 到 -256 范围内的负整数。

“x02”

整数的总大小为三个字节。后面的两个字节是一个正的小端整数。

“xfe”

整数的总大小为三个字节。接下来的两个字节是一个负的小端整数。

“x03”

整数的总大小为四个字节。接下来的三个字节是一个正的小端整数。

“xfd”

整数的总大小为四个字节。接下来的三个字节是一个负的小端整数。

“x04”

整数的总大小为五个字节。接下来的四个字节是一个正的小端整数。为了与 32 位 ruby 兼容,只有小于 1073741824 的 Fixnums 应该以这种方式表示。对于流对象的尺寸,可以使用全精度。

“xfc”

整数的总大小为五个字节。接下来的四个字节是一个负的小端整数。为了与 32 位 ruby 兼容,只有大于 -10737341824 的 Fixnums 应该以这种方式表示。对于流对象的尺寸,可以使用全精度。

否则,第一个字节是一个带偏移量的符号扩展的八位值。如果该值是正数,则该值通过从该值中减去 5 来确定。如果该值是负数,则该值通过将该值加上 5 来确定。

许多值有多种表示形式。CRuby 总是输出最短的表示形式。

符号和字节序列

“:” 代表一个真实符号。一个真实符号包含定义符号所需的数据,以便在流中后续出现时,它将成为对该符号的引用(符号链接)。该引用是一个从零开始的 32 位值(因此,:hello 的第一次出现为 0)。

在类型字节之后是字节序列,它包含一个长整型,表示序列中的字节数,以及该数量的字节数据。字节序列没有编码。

例如,以下流包含 Symbol :hello

"\x04\x08:\x0ahello"

“;” 代表一个 Symbol 链接,它引用先前定义的 Symbol。在类型字节之后是一个长整型,包含链接(引用)的 Symbol 在查找表中的索引。

例如,以下流包含 [:hello, :hello]

"\x04\b[\a:\nhello;\x00"

当下面引用“符号”时,它可以是真实符号或符号链接。

Object 引用

除了符号引用之外,流中只包含每个对象的单个副本(由 object_id 确定),对于除 true、false、nil、Fixnums 和 Symbols(如上所述,它们分别存储)之外的所有对象,将存储一个从 1 开始的 32 位值,并在再次遇到该对象时重复使用。(第一个对象的索引为 1)。

“@” 代表一个对象链接。类型字节后面是一个长整型,表示对象的索引。

例如,以下流包含一个 Array,其中包含两次相同的 "hello" 对象

"\004\b[\a\"\nhello@\006"

实例变量

“I” 表示实例变量紧随下一个对象。一个对象紧随类型字节。对象后面是一个长度,表示该对象实例变量的数量。长度后面是一组名称-值对。名称是符号,而值是对象。符号必须是实例变量名称 (:@name)。

一个 Object(“o” 类型,下面描述)使用与这里描述的相同的格式来表示其实例变量。

对于 StringRegexp(下面描述),一个特殊的实例变量 :E 用于表示 Encoding

扩展

“e” 表示下一个对象被一个模块扩展。一个对象紧随类型字节。对象后面是一个符号,包含扩展该对象的模块的名称。

Array

“[” 代表一个 Array。类型字节后面是一个长整型,表示数组中对象的个数。给定的对象数量紧随长度。

大整数

“l” 代表一个大整数,它由三个部分组成

符号

一个字节,包含正值用“+”表示,负值用“-”表示。

长度

一个长整型,表示后面跟随的大整数数据的字节数,除以二。将长度乘以二以确定后面跟随的数据字节数。

数据

表示该数字的大整数数据字节。

以下 Ruby 代码将从字节数组中重建大整数的值

result = 0

bytes.each_with_index do |byte, exp|
 result += (byte * 2 ** (exp * 8))
end

ClassModule

“c” 代表一个 Class 对象,“m” 代表一个 Module,“M” 代表一个类或模块(这是一种旧式风格,用于兼容性)。不包含任何类或模块内容,此类型只是一个引用。类型字节后面是一个字节序列,用于查找现有的类或模块。

类或模块不允许使用实例变量。

如果不存在类或模块,则应引发异常。

对于“c”和“m”类型,加载的对象必须分别是类或模块。

Data

“d”表示一个 Data 对象。(Data 对象是来自 Ruby 扩展的包装指针。)在类型字节之后是一个符号,表示 Data 对象的类,以及一个包含 Data 对象状态的对象。

要转储 Data 对象,Ruby 调用 _dump_data。要加载 Data 对象,Ruby 使用对象状态在新建的实例上调用 _load_data。

Float

“f”表示一个 Float 对象。在类型字节之后是一个包含浮点值的字节序列。以下值是特殊的

“inf”

正无穷大

“-inf”

负无穷大

“nan”

非数字

否则,字节序列包含一个 C 双精度数(可通过 strtod(3) 加载)。较旧的 Marshal 次要版本还存储了额外的尾数位,以确保跨平台的可移植性,但 4.8 不包含这些。请参阅

ruby-talk:69518

以获取一些解释。

Hash 和带有默认值的 Hash

“{”表示一个 Hash 对象,而“}”表示一个设置了默认值的 HashHash.new 0)。在类型字节之后是一个长整型,表示 Hash 中键值对的数量,即大小。在大小之后是给定数量的两倍的对象。

对于带有默认值的 Hash,默认值在所有对之后。

Module 和旧的 Module

Object

“o”表示没有其他特殊形式的对象(例如用户定义或内置格式)。在类型字节之后是一个包含对象类名的符号。在类名之后是一个长整型,表示对象实例变量名称和值的个数。在大小之后是给定数量的两倍的成对对象。

配对中的键必须是包含实例变量名称的符号。

正则表达式

“/” 代表一个正则表达式。在类型字节之后是一个包含正则表达式源代码的字节序列。在类型字节之后是一个字节,包含正则表达式选项(不区分大小写等)作为有符号 8 位值。

正则表达式可以通过实例变量附加编码(见上文)。如果没有附加编码,则必须删除以下在 ruby 1.8 中不存在的 regexp 特殊字符的转义符:g-m、o-q、u、y、E、F、H-L、N-V、X、Y。

String

‘“’ 代表一个 String。在类型字节之后是一个包含字符串内容的字节序列。从 ruby 1.9 导出时,应包含一个编码实例变量(:E,见上文),除非编码为二进制。

Struct

“S” 代表一个 Struct。在类型字节之后是一个包含结构体名称的符号。在名称之后是一个长整型,表示结构体中的成员数量。成员数量的两倍的对象紧随成员计数。每个成员都是一个包含成员符号和该成员值的对应对象的配对。

如果结构体名称与正在运行的 ruby 中的 Struct 子类不匹配,则应引发异常。

如果当前运行的 ruby 中的结构体与序列化结构体中的成员计数不匹配,则应引发异常。

用户 Class

“C” 代表 StringRegexpArrayHash 的子类。在类型字节之后是一个包含子类名称的符号。在名称之后是包装后的对象。

用户定义

“u” 代表使用 _dump 实例方法和 _load 类方法具有用户定义序列化格式的对象。在类型字节之后是一个包含类名称的符号。在类名称之后是一个包含对象用户定义表示的字节序列。

类方法 _load 在类上调用,使用从字节序列创建的字符串。

用户 Marshal

“U” 代表一个使用 marshal_dumpmarshal_load 实例方法的自定义序列化格式的对象。类型字节后面是一个包含类名的符号。类名后面是一个包含数据的对象。

加载时,必须分配一个新的实例,并使用数据在该实例上调用 marshal_load