module Marshal
Marshal 库可以将 Ruby 对象集合转换为字节流,允许它们存储在当前活动脚本之外。这些数据随后可以被读取并重新构造原始对象。
Marshal 数据在存储对象信息的同时还存储了主版本号和次版本号。在正常使用情况下,Marshal 只能加载用相同主版本号且次版本号相等或更低的版本写入的数据。如果 Ruby 的“verbose”标志已设置(通常使用 -d、-v、-w 或 –verbose),则主版本号和次版本号必须完全匹配。 Marshal 版本控制独立于 Ruby 的版本号。您可以通过读取 Marshal 数据的头两个字节来提取版本号。
str = Marshal.dump("thing") RUBY_VERSION #=> "1.9.0" str[0].ord #=> 4 str[1].ord #=> 8
某些对象无法被转储:如果要转储的对象包含绑定、过程或方法对象、IO 类实例或单例对象,则会引发 TypeError。
如果您的类有特殊的序列化需求(例如,如果您想以某种特定格式进行序列化),或者它包含通常无法序列化的对象,您可以实现自己的序列化策略。
有两种方法可以做到这一点,您的对象可以定义 `marshal_dump` 和 `marshal_load` 或 `_dump` 和 `_load`。如果两者都已定义,`marshal_dump` 将优先于 `_dump`。`marshal_dump` 可能会产生更小的 Marshal 字符串。
安全注意事项
根据设计,Marshal.load 可以反序列化几乎所有加载到 Ruby 进程中的类。在许多情况下,如果 Marshal 数据来自不受信任的源,这可能导致远程代码执行。
因此,Marshal.load 不适合作为通用序列化格式,您永远不应该反序列化用户提供的输入或其他不受信任的数据。
如果您需要反序列化不受信任的数据,请使用 JSON 或其他只能加载简单“原始”类型的序列化格式,例如 String、Array、Hash 等。切勿允许用户输入指定任意类型进行反序列化。
marshal_dump 和 marshal_load
转储对象时将调用 `marshal_dump` 方法。`marshal_dump` 必须返回一个包含 `marshal_load` 重构对象所需信息的 `result`。`result` 可以是任何对象。
当加载使用 `marshal_dump` 转储的对象时,对象首先被分配,然后调用 `marshal_load`,并传入 `marshal_dump` 的 `result`。`marshal_load` 必须从 `result` 中的信息重新创建对象。
示例
class MyObj def initialize name, version, data @name = name @version = version @data = data end def marshal_dump [@name, @version] end def marshal_load array @name, @version = array end end
_dump 和 _load
当您需要自己分配要还原的对象时,请使用 `_dump` 和 `_load`。
转储对象时,将调用实例方法 `_dump`,并传入一个 Integer,该整数指示要转储的最大对象深度(值为 -1 表示应禁用深度检查)。`_dump` 必须返回一个包含重构对象所需信息的 String。
类方法 `_load` 应该接受一个 String 并使用它来返回同一类的对象。
示例
class MyObj def initialize name, version, data @name = name @version = version @data = data end def _dump level [@name, @version].join ':' end def self._load args new(*args.split(':')) end end
由于 Marshal.dump 输出一个字符串,因此您可以让 `_dump` 返回一个 Marshal 字符串,该字符串在 `_load` 中由 `Marshal.loaded` 进行解析,以处理复杂对象。
Constants
- MAJOR_VERSION
-
主版本号
- MINOR_VERSION
-
次版本号
Public Class Methods
Source
static VALUE
marshal_dump(int argc, VALUE *argv, VALUE _)
{
VALUE obj, port, a1, a2;
int limit = -1;
port = Qnil;
rb_scan_args(argc, argv, "12", &obj, &a1, &a2);
if (argc == 3) {
if (!NIL_P(a2)) limit = NUM2INT(a2);
if (NIL_P(a1)) io_needed();
port = a1;
}
else if (argc == 2) {
if (FIXNUM_P(a1)) limit = FIX2INT(a1);
else if (NIL_P(a1)) io_needed();
else port = a1;
}
return rb_marshal_dump_limited(obj, port, limit);
}
序列化 obj 和所有后代对象。如果指定了 anIO,则序列化数据将写入其中,否则数据将作为 String 返回。如果指定了 limit,则子对象的遍历将限制在该深度。如果 limit 为负数,则不执行深度检查。
class Klass def initialize(str) @str = str end def say_hello @str end end
(不产生任何输出)
o = Klass.new("hello\n") data = Marshal.dump(o) obj = Marshal.load(data) obj.say_hello #=> "hello\n"
Marshal 无法转储以下对象
-
匿名类/模块。
-
与系统相关的对象(例如:
Dir、File::Stat、IO、File、Socket等) -
MatchData、Method、UnboundMethod、Proc、Thread、ThreadGroup、Continuation的实例 -
定义了单例方法的对象
Source
# File marshal.rb, line 33 def self.load(source, proc = nil, freeze: false) Primitive.marshal_load(source, proc, freeze) end
返回将 source 中的序列化数据转换为 Ruby 对象(可能包含关联的从属对象)的结果。source 可以是 IO 的实例,也可以是响应 to_str 的对象。如果指定了 proc,则在反序列化每个对象时,都会将该对象传递给 proc。
切勿将不受信任的数据(包括用户提供的输入)传递给此方法。请参阅概述以获取更多详细信息。
如果传递了 freeze: true 参数,则反序列化的对象将被深度冻结。请注意,由于冻结字符串的去重,这可能会导致更高效的内存使用。
serialized = Marshal.dump(['value1', 'value2', 'value1', 'value2']) deserialized = Marshal.load(serialized) deserialized.map(&:frozen?) # => [false, false, false, false] deserialized.map(&:object_id) # => [1023900, 1023920, 1023940, 1023960] -- 4 different objects deserialized = Marshal.load(serialized, freeze: true) deserialized.map(&:frozen?) # => [true, true, true, true] deserialized.map(&:object_id) # => [1039360, 1039380, 1039360, 1039380] -- only 2 different objects, object_ids repeating