先来个老段子:
引用
自从深发展银行推出那条知性的广告语“只想与你深发展”后,银行业内人士又自编出了更知性的姊妹篇:“光大是不行的”
正题:研究一下ruby中深拷贝(deep clone)的问题。
1.=
ruby里拷贝对象的最简单方法,下面来看一下效果:
irb(main):002:0> a = "Hooopo"
=> "Hooopo"
irb(main):003:0> b = a
=> "Hooopo"
irb(main):004:0> b.object_id
=> 23424840
irb(main):005:0> a.object_id
=> 23424840
irb(main):006:0> b.gsub!("o","-")
=> "H---p-"
irb(main):007:0> p a
"H---p-"
=> nil
irb(main):008:0> p b
"H---p-"
=> nil
b修改后,a也跟着修改了,并且a和b的object_id一样,就是同一个对象嘛~
2.dup和clone(dup和clone差不多,但是还是有一些区别的)
irb(main):001:0> a = "Hooopo"
=> "Hooopo"
irb(main):002:0> b = a.dup
=> "Hooopo"
irb(main):003:0> a.object_id
=> 23428740
irb(main):004:0> b.object_id
=> 23425010
irb(main):005:0> b.gsub!("o","-")
=> "H---p-"
irb(main):006:0> p b
"H---p-"
=> nil
irb(main):007:0> p a
"Hooopo"
=> nil
irb(main):008:0>
b和a是不同对象,并且改变b后a不改变。这是我们想要的。但是别高兴太早,看下面例子:
class Klass
attr_accessor :str
end
s1 = Klass.new
s1.str = "Hooopo"
p s1
s2 = s1.dup
p s2
s2.str.gsub!("o","-")
p s1
p s2
#results
#<Klass:0x2d2cd30 @str="Hooopo">
#<Klass:0x2d2cc7c @str="Hooopo">
#<Klass:0x2d2cd30 @str="H---p-">
#<Klass:0x2d2cc7c @str="H---p-">
显然,问题又有了,改变s2后s1也跟着改变了。同样的陷阱也发生在Array和Hash里,看下面代码:
irb(main):008:0> arr = [1,[1,1]]
=> [1, [1, 1]]
irb(main):009:0> arr_dup = arr.dup
=> [1, [1, 1]]
irb(main):010:0> arr_dup[0] = 2
=> 2
irb(main):011:0> arr
=> [1, [1, 1]]
irb(main):012:0> arr_dup[1][0] = 2
=> 2
irb(main):013:0> arr
=> [1, [2, 1]]
在第一次给arr_dup[0]复值的时候arr没有改变,而第二次改变arr_dup[1][0]的时候arr也跟着改变了。就是说Array#dup只拷贝了一层,还不够深入呀..同样Hash也是,看下面代码:
irb(main):001:0> hash = {:key => {:key => "value"}}
=> {:key=>{:key=>"value"}}
irb(main):002:0> hash_dup = hash.dup
=> {:key=>{:key=>"value"}}
irb(main):003:0> hash_dup[:key][:key] = "value_changed"
=> "value_changed"
irb(main):004:0> hash_dup
=> {:key=>{:key=>"value_changed"}}
irb(main):005:0> hash
=> {:key=>{:key=>"value_changed"}}
3.用序列化实现深拷贝
irb(main):014:0> arr = [1,[1,1]]
=> [1, [1, 1]]
irb(main):015:0> arr_dump = Marshal.load(Marshal.dump(arr))
=> [1, [1, 1]]
irb(main):016:0> arr_dump.object_id
=> 22807940
irb(main):017:0> arr.object_id
=> 22850200
irb(main):018:0> arr_dump[1][1] = 2
=> 2
irb(main):019:0> arr_dump
=> [1, [1, 2]]
irb(main):020:0> arr
=> [1, [1, 1]]
irb(main):012:0> hash = {:key => {:key => "value"}}
=> {:key=>{:key=>"value"}}
irb(main):013:0> hash_dump = Marshal.load(Marshal.dump(hash))
=> {:key=>{:key=>"value"}}
irb(main):014:0> hash.object_id
=> 22790990
irb(main):015:0> hash_dump.object_id
=> 22755440
irb(main):016:0> hash_dump[:key][:key] = "value not changed"
=> "value not changed"
irb(main):017:0> hash
=> {:key=>{:key=>"value"}}
irb(main):018:0> hash_dump
=> {:key=>{:key=>"value not changed"}}
情况似乎好多了。但是还有一个问题,就是Marshal只能序列化一般对象,数组哈希,高级一些的对象不能序列化(IO,Proc,singleton等)
下面是两个deep clone的实现(via:http://www.artima.com/forums/flat.jsp?forum=123&thread=40913)
class Object
def deep_clone
Marshal::load(Marshal.dump(self))
end
end
class Object
def dclone
case self
when Fixnum,Bignum,Float,NilClass,FalseClass,
TrueClass,Continuation
klone = self
when Hash
klone = self.clone
self.each{|k,v| klone[k] = v.dclone}
when Array
klone = self.clone
klone.clear
self.each{|v| klone << v.dclone}
else
klone = self.clone
end
klone.instance_variables.each {|v|
klone.instance_variable_set(v,
klone.instance_variable_get(v).dclone)
}
klone
end
end
更复杂的(via:http://d.hatena.ne.jp/pegacorn/20070417/1176817721)...
class Object
def deep_clone
_deep_clone({})
end
protected
def _deep_clone(cloning_map)
return cloning_map[self] if cloning_map.key? self
cloning_obj = clone
cloning_map[self] = cloning_obj
cloning_obj.instance_variables.each do |var|
val = cloning_obj.instance_variable_get(var)
begin
val = val._deep_clone(cloning_map)
rescue TypeError
next
end
cloning_obj.instance_variable_set(var, val)
end
cloning_map.delete(self)
end
end
class Array
protected
def _deep_clone(cloning_map)
return cloning_map[self] if cloning_map.key? self
cloning_obj = super
cloning_map[self] = cloning_obj
cloning_obj.map! do |val|
begin
val = val._deep_clone(cloning_map)
rescue TypeError
#
end
val
end
cloning_map.delete(self)
end
end
class Hash
protected
def _deep_clone(cloning_map)
return cloning_map[self] if cloning_map.key? self
cloning_obj = super
cloning_map[self] = cloning_obj
pairs = cloning_obj.to_a
cloning_obj.clear
pairs.each do |pair|
pair.map! do |val|
begin
val = val._deep_clone(cloning_map)
rescue TypeError
#
end
val
end
cloning_obj[pair[0]] = pair[1]
end
cloning_map.delete(self)
end
end
Something about dup and clone
1.ruby字面量(Fixnum,true,false,nil,Symbol)不可以调用dup和clone方法,会报TypeError。
2.对对象的state(taint,frozen)的改变:dup会把frozen的对象unfrozen,clone不会。
irb(main):019:0> o = Object.new
=> #<Object:0x2b7855c>
irb(main):020:0> o.taint
=> #<Object:0x2b7855c>
irb(main):021:0> o.freeze
=> #<Object:0x2b7855c>
irb(main):024:0> [o.frozen?,o.tainted?]
=> [true, true]
irb(main):025:0> o_clone = o.clone
=> #<Object:0x2b44c0c>
irb(main):026:0> [o_clone.frozen?,o_clone.tainted?]
=> [true, true]
irb(main):027:0> o_dup = o.dup
=> #<Object:0x2b32e6c>
irb(main):028:0> [o_dup.frozen?,o_dup.tainted?]
=> [false, true]
3.对单体方法的拷贝:clone会连同单体方法一起拷贝,dup不会。
irb(main):029:0> o = Object.new
=> #<Object:0x2b256a4>
irb(main):030:0> def o.say
irb(main):031:1> puts "Hello,Hooopo"
irb(main):032:1> end
=> nil
irb(main):033:0> o_dup = o.dup
=> #<Object:0x296acec>
irb(main):035:0> o_clone = o.clone
=> #<Object:0x2dba194>
irb(main):037:0> o_dup.say
NoMethodError: undefined method `say' for #<Object:0x296acec>
from (irb):37
from :0
irb(main):038:0> o_clone.say
Hello,Hooopo