Marshal 模块

Marshal 库将 Ruby 对象集合转换为字节流,允许它们存储在当前活动脚本之外。这些数据随后可以被读取,并且可以重建原始对象。

Marshaled 数据包含与对象信息一起存储的主版本号和次版本号。在正常使用中,Marshal 只能加载使用相同主版本号和等于或更低的次版本号写入的数据。如果 Ruby 的“verbose”标志被设置(通常使用 -d、-v、-w 或 –verbose),则主版本号和次版本号必须完全匹配。 Marshal 版本控制独立于 Ruby 的版本号。您可以通过读取 Marshaled 数据的前两个字节来提取版本。

str = Marshal.dump("thing")
RUBY_VERSION   #=> "1.9.0"
str[0].ord     #=> 4
str[1].ord     #=> 8

某些对象无法被 dump:如果要 dump 的对象包含绑定、过程或方法对象、类 IO 的实例或单例对象,则会引发 TypeError

如果您的类有特殊的序列化需求(例如,如果您想以某种特定格式序列化),或者如果它包含否则无法序列化的对象,您可以实现自己的序列化策略。

有两种方法可以做到这一点,您的对象可以定义 marshal_dump 和 marshal_load 或 _dump 和 _load。如果同时定义了 marshal_dump 和 _dump,则 marshal_dump 优先。marshal_dump 可能导致更小的 Marshal 字符串。

安全注意事项

根据设计,Marshal.load 可以反序列化加载到 Ruby 进程中的几乎所有类。在许多情况下,如果 Marshal 数据从不受信任的来源加载,这会导致远程代码执行。

因此,Marshal.load 不适合作为通用序列化格式,您永远不应该反序列化用户提供的输入或其他不受信任的数据。

如果您需要反序列化不受信任的数据,请使用 JSON 或其他仅能加载简单“原始”类型的序列化格式,例如 StringArrayHash 等。永远不要允许用户输入指定任意类型来反序列化。

marshal_dump 和 marshal_load

在 dump 对象时,将调用 marshal_dump 方法。marshal_dump 必须返回一个结果,该结果包含 marshal_load 重建对象所需的信息。结果可以是任何对象。

在加载使用 marshal_dump dump 的对象时,首先分配对象,然后使用 marshal_dump 的结果调用 marshal_load。marshal_load 必须从结果中的信息重建对象。

示例

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 用于复杂对象。

常量

MAJOR_VERSION

主版本

MINOR_VERSION

次版本

公共类方法

dump( obj [, anIO] , limit=-1 ) → anIO click to toggle source

序列化 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 无法转储以下对象

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);
}
load(source, proc = nil, freeze: false) → obj click to toggle source

返回将 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
# File ruby_3_3_0/marshal.rb, line 33
def self.load(source, proc = nil, freeze: false)
  Primitive.marshal_load(source, proc, freeze)
end
也称为:restore
restore(source, proc = nil, freeze: false) → obj
别名:load