我知道序列化一个对象是(据我所知)有效深度复制一个对象的唯一方法(只要它不像IO
那样有状态),但是一种方式特别多效率高于另一个?
例如,由于我正在使用Rails,我总是可以使用ActiveSupport::JSON
,to_xml
- 从我可以告诉编组的对象是最常用的方法之一。我希望编组可能是最有效的,因为它是一个Ruby内部,但我错过了什么?
编辑:请注意,我已经介绍了它的实现 - 我不想替换现有的浅拷贝方法(如dup
和clone
),所以我最终可能会添加Object::deep_copy
,其结果是上述方法中的任何一种(或任何建议:)具有最小的开销。
答案 0 :(得分:21)
我想知道同样的事情,所以我对几种不同的技术进行了基准测试。我主要关注数组和哈希 - 我没有测试任何复杂的对象。也许不出所料,自定义深度克隆实现被证明是最快的。如果您正在寻找快速简便的实施方案,那么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)
答案 1 :(得分:1)
我认为您需要在要复制的类中添加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
我确信你可以通过一点点的修补来制造更酷的但是无论好坏,这可能就是我的意思。
答案 2 :(得分:0)
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 = 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
尝试克隆未以标准方式克隆自身的对象,则可能会失败。deepclone
尝试克隆一个可以以标准方式克隆自身的对象,并且如果它是一个复杂的结构,它可能(并且可能会)对其自身进行浅层克隆。deepclone
没有深层复制哈希中的键。原因是它们通常不被视为数据,但如果您将hash[k]
更改为hash[k.deepclone]
,它们也会被深层复制。new
方法,例如Fixnum。这些对象始终具有相同的对象ID,并且是复制的,而不是克隆的。