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

| 我知道序列化对象(据我所知)是有效深度复制对象的唯一方法(只要它没有像“ 0”之类的有状态的东西),但是一种方法比另一种方法特别有效? 例如,由于我使用的是Rails,所以我总是可以使用
ActiveSupport::JSON
to_xml
-从我可以看出,将对象编组是执行此操作的最常用方法之一。我希望封送处理可能是其中最有效的一种,因为它是Ruby内部的,但是我有什么遗漏吗? 编辑:请注意,它的实现是我已经讲过的-我不想替换现有的浅表复制方法(例如
dup
clone
),所以我最终可能会添加
Object::deep_copy
,无论结果如何以上方法(或您的任何建议:)中开销最小的方法。     
已邀请:
我想知道同一件事,所以我相互比较了一些不同的技术。我主要关心数组和哈希-我没有测试任何复杂的对象。也许不足为奇的是,自定义深度克隆实现被证明是最快的。如果您正在寻找快速简便的实施方案,那么元帅似乎是您的理想之选。 我还使用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在Rails上,所以请告诫Empor。就个人而言,我不建议您这样做。 例如:
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 = m.z.deepclone
    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                                        => #<M:0x0000000213fd88 @z=nil>
# a new object of M with m.z pre-set to \'g\'
m=M.new(nil,\'g\')                               => #<M:0x00000002134ca8 @z=\"g\">
# a deepclone of m in which the strings are the same value, but different objects
n=m.deepclone                                  => #<M:0x00000002131d00 @z=\"g\">
puts \"#{m.z.object_id} / #{n.z.object_id}\" => 17409660 / 17403500
其中M类的对象是数组的一部分:
a = {\'a\'=>M.new(nil,\'g\'),\'b\'=>\'y\'}               => {\"a\"=>#<M:0x00000001f8bf78 @z=\"g\">, \"b\"=>\"y\"}
b = a.deepclone                                  => {\"a\"=>#<M:0x00000001766f28 @z=\"g\">, \"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
尝试克隆不以标准方式克隆自身的对象,则可能会失败。 如果ѭ13尝试克隆可以以标准方式克隆自己的对象,并且它是一个复杂的结构,则它可能(并且可能会)对其自身进行浅表克隆。
deepclone
不会深度复制哈希中的密钥。原因是它们通常不被视为数据,但是如果将
hash[k]
更改为
hash[k.deepclone]
,它们也将被深复制。 某些元素值没有
new
方法,例如Fixnum。这些对象始终具有相同的对象ID,并且会被复制而不是克隆。 请注意,因为在进行深层复制时,原始对象中包含相同对象的哈希或数组的两个部分在深层克隆中将包含不同的对象。     

要回复问题请先登录注册