Marshal 格式

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 位值。类型后跟一到五个字节。加载的值始终是 Fixnum。在 32 位平台(其中 Fixnum 的精度小于 32 位)上加载大值会导致 CRuby 上的溢出。

fixnum 类型用于表示 ruby Fixnum 对象以及 marshaled 数组、哈希表、实例变量和其他类型的大小。在以下部分中,“long”将表示下面描述的格式,该格式支持完整的 32 位精度。

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

“x00”

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

“x01”

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

“xff”

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

“x02”

整数的总大小为三个字节。后面的两个字节是正的 little-endian 整数。

“xfe”

整数的总大小为三个字节。后面的两个字节是负的 little-endian 整数。

“x03”

整数的总大小为四个字节。后面的三个字节是正的 little-endian 整数。

“xfd”

整数的总大小为四个字节。后面的三个字节是负的 little-endian 整数。

“x04”

整数的总大小为五个字节。后面的四个字节是正的 little-endian 整数。为了与 32 位 ruby 兼容,只有小于 1073741824 的 Fixnum 才应该以这种方式表示。对于流对象的大小,可以使用完整精度。

“xfc”

整数的总大小为五个字节。后面的四个字节是负的 little-endian 整数。为了与 32 位 ruby 兼容,只有大于 -10737341824 的 Fixnum 才应该以这种方式表示。对于流对象的大小,可以使用完整精度。

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

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

符号和字节序列

“:”表示一个真正的符号。真正的符号包含定义流中其余符号所需的数据,因为流中将来出现的符号将改为对此符号的引用(符号链接)。该引用是一个从零开始索引的 32 位值(因此第一个出现的 :hello 是 0)。

在类型字节之后是字节序列,该序列由一个 long 指示序列中的字节数,后跟相应数量的数据字节。字节序列没有编码。

例如,以下流包含 Symbol :hello

"\x04\x08:\x0ahello"

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

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

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

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

Object 引用

与符号引用分开但类似,该流对于所有对象(true、false、nil、Fixnum 和 Symbols 除外,它们如上所述单独存储)仅包含每个对象的一个副本(由 object_id 确定),当再次遇到该对象时,将存储和重用一个从 1 开始索引的 32 位值。(第一个对象的索引为 1)。

“@”表示对象链接。在类型字节之后是一个 long,给出对象的索引。

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

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

实例变量

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

Object(“o”类型,如下所述)为其实例变量使用与此处描述的相同的格式。

对于 StringRegexp(如下所述),使用特殊的实例变量 :E 来指示 Encoding

扩展

“e”表示下一个对象由模块扩展。一个对象在类型字节之后。在该对象之后是一个符号,其中包含该对象所扩展的模块的名称。

Array

“[”表示一个 Array。在类型字节之后是一个 long,指示数组中对象的数量。给定数量的对象跟在长度后面。

Bignum

“l”表示一个 Bignum,它由三个部分组成

符号

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

长度

一个 long,指示 Bignum 数据字节数(除以 2)。将长度乘以 2 以确定后面数据字节数。

数据

表示数字的 Bignum 数据字节。

以下 ruby 代码将从字节数组中重建 Bignum 值

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 double (可以通过 strtod(3) 加载)。旧版本 Marshal 的次要版本也存储额外的尾数位,以确保跨平台的移植性,但 4.8 版本不包含这些。请参阅

ruby-talk:69518

获取一些解释。

Hash 和带有默认值的 Hash

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

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

Module 和旧的 Module

Object

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

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

正则表达式

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

正则表达式可以通过实例变量附加一个编码(见上文)。如果未附加任何编码,则必须删除 ruby 1.8 中不存在的以下正则表达式特殊字符的转义: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