Ruby Marshal.load 不保持排序集的顺序

Ruby Marshal.load doesn't keep order of sorted set

我正在使用 Marshal.dump 在文件中保存一个 SortedSet 对象。 集合中的元素也是对象(包括 Comparable 并实现 <=> 方法)。

稍后使用 Marshal.load 恢复该对象时,从文件加载的 SortedSet 未排序...

知道为什么或如何解决它吗?

这是一个重现问题的简化示例:

require 'set'
class Foo
  include Comparable

  attr_accessor :num

  def initialize(num)
    @num = num
  end

  def <=>(other)
    num <=> other.num
  end
end

f1 = Foo.new(1)
f2 = Foo.new(2)
f3 = Foo.new(3)

s = SortedSet.new([f2, f1, f3])

File.open('set_test.dump', 'wb') { |f| Marshal.dump(s, f) }

然后,从我使用的文件加载对象 -

File.open('set_test.dump', 'rb') { |f| ls = Marshal.load(f) }

** 我正在使用 Rails 3.2.3 和 Ruby 2.1.8

** 从文件加载转储时 - 在 new/seperate rails 控制台中执行(不要忘记复制粘贴 Foo class 的定义:-))

重现错误

我可以在每一次 Ruby 尝试时重现此行为。

# write_sorted_set.rb
require 'set'
class Foo
  include Comparable

  attr_accessor :num

  def initialize(num)
    @num = num
  end

  def <=>(other)
    num <=> other.num
  end
end

f1 = Foo.new(1)
f2 = Foo.new(2)
f3 = Foo.new(3)

s = SortedSet.new([f2, f1, f3])
File.open('set_test.dump', 'wb') { |f| Marshal.dump(s, f) }
p s.to_a

# load_sorted_set.rb
require 'set'
class Foo
  include Comparable

  attr_accessor :num

  def initialize(num)
    @num = num
  end

  def <=>(other)
    num <=> other.num
  end
end

ls = Marshal.load(File.binread('set_test.dump'))
p ls.to_a

启动时

ruby write_sorted_set.rb && ruby load_sorted_set.rb

输出

[#<Foo:0x000000010cae30 @num=1>, #<Foo:0x000000010cae08 @num=2>, #<Foo:0x000000010cadb8 @num=3>]
[#<Foo:0x0000000089be08 @num=2>, #<Foo:0x0000000089bd18 @num=1>, #<Foo:0x0000000089bc78 @num=3>]

为什么?

未使用可比值

使用这个定义:

class Foo
  attr_accessor :num
  def initialize(num)
    @num = num
  end
end

in load_sorted_set.rb 应该引发异常 (comparison of Foo with Foo failed (ArgumentError)),但它没有。看起来 SortedSet 没有被 Marshal.load

正确初始化

lib/set.rb

正在查看 SortedSetsourcecode :

  module_eval {
    # a hack to shut up warning
    alias old_init initialize
  }

      module_eval {
        # a hack to shut up warning
        remove_method :old_init
      }

      @@setup = true
    end
  end

  def initialize(*args, &block) # :nodoc:
    SortedSet.setup
    initialize(*args, &block)
  end
end

看起来 SortedSet 已被修补以确保 SortedSet.setup 在任何 SortedSet 初始化之前执行。

Marshal.load好像不知道这个

解决方案

SortedSet.setup

你可以打电话

SortedSet.setup

require 'set'之后和Marshal.load之前

SortedSet.new

您可以使用 :

强制执行 SortedSet 初始化
ls = SortedSet.new(Marshal.load(File.binread('set_test.dump')))