在Ruby中深度复制对象的最有效方法是什么?

我知道序列化一个对象是(据我所知)有效深度复制一个对象的唯一方法(只要它不像IO和诸如此类的状态),但是这种方式比另一种方式更有效吗?

例如,因为我正在使用Rails,所以我总是可以使用ActiveSupport::JSONto_xml – 从我可以告诉编组的对象是最常被接受的方法之一。 我希望编组可能是最有效的,因为它是一个Ruby内部,但我错过了什么?

编辑 :请注意,它的实现是我已经涵盖的内容 – 我不想替换现有的浅拷贝方法(如dupclone ),所以我最终可能会添加Object::deep_copy ,其结果是无论上述哪种方法(或任何建议:)都有最少的开销。

我想知道同样的事情,所以我对几种不同的技术进行了对比。 我主要关注数组和哈希 – 我没有测试任何复杂的对象。 也许不出所料,自定义深度克隆实现被certificate是最快的。 如果您正在寻找快速简便的实施方案,那么Marshal似乎就是您要走的路。

我还使用Rails 3.0.7对XML解决方案进行了基准测试,未在下面显示。 只有1000次迭代,它的速度要慢得多,大约10秒钟(以下解决方案对于基准测试都运行了10,000次)。

关于我的JSON解决方案的两个注释。 首先,我使用了C版本1.4.3。 其次,它实际上不会100%工作,因为符号将转换为字符串。

这完全是用ruby 1.9.2p180运行的。

 #!/usr/bin/env ruby require 'benchmark' require 'yaml' require 'json/ext' require 'msgpack' def dc1(value) Marshal.load(Marshal.dump(value)) end def dc2(value) YAML.load(YAML.dump(value)) end def dc3(value) JSON.load(JSON.dump(value)) end def dc4(value) if value.is_a?(Hash) result = value.clone value.each{|k, v| result[k] = dc4(v)} result elsif value.is_a?(Array) result = value.clone result.clear value.each{|v| result << dc4(v)} result else value end end def dc5(value) MessagePack.unpack(value.to_msgpack) end value = {'a' => {:x => [1, [nil, 'b'], {'a' => 1}]}, 'b' => ['z']} Benchmark.bm do |x| iterations = 10000 x.report {iterations.times {dc1(value)}} x.report {iterations.times {dc2(value)}} x.report {iterations.times {dc3(value)}} x.report {iterations.times {dc4(value)}} x.report {iterations.times {dc5(value)}} end 

结果是:

 user system total real 0.230000 0.000000 0.230000 ( 0.239257) (Marshal) 3.240000 0.030000 3.270000 ( 3.262255) (YAML) 0.590000 0.010000 0.600000 ( 0.601693) (JSON) 0.060000 0.000000 0.060000 ( 0.067661) (Custom) 0.090000 0.010000 0.100000 ( 0.097705) (MessagePack) 

我认为您需要在要复制的类中添加initialize_copy方法。 然后将深拷贝的逻辑放在那里。 然后,当您调用clone时,它将触发该方法。 我还没有这样做,但这是我的理解。

我认为B计划只会覆盖克隆方法:

 class CopyMe attr_accessor :var def initialize var='' @var = var end def clone deep= false deep ? CopyMe.new(@var.clone) : CopyMe.new() end end a = CopyMe.new("test") puts "A: #{a.var}" b = a.clone puts "B: #{b.var}" c = a.clone(true) puts "C: #{c.var}" 

产量

 mike@sleepycat:~/projects$ ruby ~/Desktop/clone.rb A: test B: C: test 

我相信你可以通过一点点的修补来制造更酷的温度,但不管是好还是坏,这可能就是我要做的。

可能Ruby不包含深度克隆的原因与问题的复杂性有关。 请参阅最后的注释。

要制作一个“深度复制”,哈希,数组和元素值的克隆,即复制原始中的每个元素,使副本具有相同的值,但新对象,您可以使用:

 class Object def deepclone case when self.class==Hash hash = {} self.each { |k,v| hash[k] = v.deepclone } hash when self.class==Array array = [] self.each { |v| array << v.deepclone } array else if defined?(self.class.new) self.class.new(self) else self end end end end 

如果你想重新定义Ruby的clone方法的行为,你可以将它命名为clone而不是deepclone (在3个地方),但我不知道重新定义Ruby的克隆行为将如何影响Ruby库或Ruby on Rails,所以Caveat Emptor 。 就个人而言,我不建议这样做。

例如:

 a = {'a'=>'x','b'=>'y'} => {"a"=>"x", "b"=>"y"} b = a.deepclone => {"a"=>"x", "b"=>"y"} puts "#{a['a'].object_id} / #{b['a'].object_id}" => 15227640 / 15209520 

如果你希望你的类能够正确地深度克隆,他们的new方法(初始化)必须能够以标准方式深度克隆该类的对象,即,如果给出第一个参数,则假定它是要被深度克隆的对象。

例如,假设我们想要一个M类。 第一个参数必须是类M的可选对象。这里我们有第二个可选参数z来预先设置新对象中的z值。

 class M attr_accessor :z def initialize(m=nil, z=nil) if m # deepclone all the variables in m to the new object @z = mzdeepclone else # default all the variables in M @z = z # default is nil if not specified end end end 

在此克隆过程中会忽略z预设,但您的方法可能有不同的行为。 这个类的对象将像这样创建:

 # a new 'plain vanilla' object of M m=M.new => # # a new object of M with mz pre-set to 'g' m=M.new(nil,'g') => # # a deepclone of m in which the strings are the same value, but different objects n=m.deepclone => # puts "#{mzobject_id} / #{nzobject_id}" => 17409660 / 17403500 

M类的对象是数组的一部分:

 a = {'a'=>M.new(nil,'g'),'b'=>'y'} => {"a"=>#, "b"=>"y"} b = a.deepclone => {"a"=>#, "b"=>"y"} puts "#{a['a'].object_id} / #{b['a'].object_id}" => 12303600 / 12269460 puts "#{a['b'].object_id} / #{b['b'].object_id}" => 16811400 / 17802280 

笔记:

  • 如果deepclone尝试克隆一个没有以标准方式克隆自身的对象,它可能会失败。
  • 如果deepclone试图克隆一个可以以标准方式克隆自身的对象,并且如果它是一个复杂的结构,它可能(并且可能会)自己做一个浅层克隆。
  • deepclone不会深入复制deepclone中的键。 原因是它们通常不被视为数据,但如果将hash[k]更改为hash[k.deepclone]它们也将被深度复制。
  • 某些元素值没有new方法,例如Fixnum。 这些对象始终具有相同的对象ID,并且是复制的,而不是克隆的。
  • 请注意,因为深度复制时,原始中包含相同对象的Hash或Array的两个部分将包含deepclone中的不同对象。