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 对象以及编组数组、哈希、实例变量和其他类型的大小。在以下部分中,“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 和 Symbol 除外(它们单独存储,如上所述))仅包含每个对象的一个副本(由 object_id 确定),当再次遇到该对象时,将存储和重用一个从一开始的 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 数据的字节数,除以二。将长度乘以二以确定后面数据的字节数。

数据

表示数字的 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