`
Hooopo
  • 浏览: 335143 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

只想与你深发展..

    博客分类:
  • Ruby
阅读更多
先来个老段子:
引用

自从深发展银行推出那条知性的广告语“只想与你深发展”后,银行业内人士又自编出了更知性的姊妹篇:“光大是不行的”


正题:研究一下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



分享到:
评论
5 楼 orcl_zhang 2009-12-10  
Hooopo分析真是透彻。看下api。
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference. dup copies the tainted state of obj. See also the discussion under Object#clone. In general, clone and dup may have different semantics in descendent classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendent object to create the new instance.
分析下。
1,Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
借用下Hooopo的代码。希望不要介意。
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对象,存储的"Hooopo",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-">  

s1,s2虽然是不同的对象,但是对象内储的却是Klass一个实例的地址。所以会改变。
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]]

attr数组内存储的是1和[2,1]的地址,所以attr[0]改变不会受影响,而attr[1]则会受影响。
2,While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendent object to create the new instance.
最后这句是关键。clone是在复制对象,包括对象的状态,而dup则是从其派生类创建一个新的实例。所以对于attr=[1, [1, 1]],attr[0]存的是integer,会创建integer类型,attr[1]是一个数组类型,dup只能简单的复制attr[1]中用来保存[1,1]的地址的类的一个新实例。
对于hash,或者更复杂的类,我想原理应该是相同的。
有什么不对的,还希望能指出来。
4 楼 night_stalker 2009-06-14  
1.8 可以用:
require 'ruby2ruby'
module X
  def x
    'x'
  end
end
Ruby2Ruby.translate X
#=> "module X\n  def x\n    \"x\"\n  end\nend"


但是 1.9 不能用……
3 楼 Hooopo 2009-06-14  
http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/8c08df930291a2e9/3cccf36faad55d8c?hl=zh-cn&lnk=gst&q=dump+module#3cccf36faad55d8c

How about the following?  The only major disadvantage is that it will be a
little bit slower than using a real Proc:
require 'delegate' 
class DumpableProc < DelegateClass(Proc) 
  def initialize(str) 
    eval "@proc = proc { #{str} }" 
    @str = str 
    super(@proc) 
  end 
  def _dump(limit) 
    @str 
  end 
  def self._load(str) 
    self.new(str) 
  end 
end 
dp = DumpableProc.new("puts 'foo!'") 
dp.call() 
dump_str = Marshal.dump(dp) 
puts dump_str 
dp2 = Marshal.load(dump_str) 
dp2.call() 

2 楼 Hooopo 2009-06-14  
<div class="quote_title">night_stalker 写道</div>
<div class="quote_div">Array、Hash、实例变量 已经足够表达数据结构了。 data 易 dump,class 不易 dump ……<br><br>现在最想要的是 Binding 和 Proc 的 Marshal.dump, 或者 dump module 也行……</div>
<p><br><span style="font-family: 'times new roman', times;"><span style="font-size: medium;">|I can understand why Thread and the IOs can't be dumped (because there <br>|are underlying operating system structures associated with them), but <br>|can't see why Proc, Continuation and Method couldn't be dumped/loaded ... <br>|although I could see that it might not be possible in the special case <br>|where their bindings included a Thread or an IO of some form. <br>| <br>|Is it just that it's considered too hard to get right (and I'm not saying <br>|it would be easy!) or is there some other reason? <br>Procs, Bindings and Continuations contain references to C stack <br>information which is not portable.  Methods have references to C <br>function.  All of above information is not portable. <br>                                                        matz. </span></span></p>
<p> </p>
1 楼 night_stalker 2009-06-14  
Array、Hash、实例变量 已经足够表达数据结构了。 data 易 dump,class 不易 dump ……

现在最想要的是 Binding 和 Proc 的 Marshal.dump, 或者 dump module 也行……

相关推荐

    深发展与平安银行.doc

    深发展与平安银行.doc

    深发展与平安银行.docx

    【标题】: "深发展与平安银行合并的IT技术整合分析" 【描述】: 这份文档虽然提及的是南昌大众交通公司与江西长运股份公司的资产评估案例,但考虑到标签为"技术",我们可以推测其中可能涉及到银行合并过程中的IT系统...

    平安并购深发展案例分析.pptx

    【平安并购深发展案例分析】 该案例主要涉及的是中国平安集团对深圳发展银行(简称深发展)的并购过程,这是一起在中国金融市场上具有重要意义的并购事件。平安集团,成立于1988年,是中国领先的综合金融服务集团,...

    XXXX年抗通胀理财推介会(深发展).pptx

    【XXXX年抗通胀理财推介会(深发展).pptx】这份文档是关于2011年深发展银行举办的一场抗通胀理财活动的介绍,重点讨论了如何通过理财策略应对当时的通货膨胀压力。文档中提到的讲师是国家注册黄金分析师何伟锋,他...

    国内保理业务合同【深发展】.pdf

    深发展银行的这份合同详细规定了保理业务的操作流程、各方权利和责任。 1. **保理业务**:保理是一种金融服务,通过银行收购企业的应收账款,企业可以提前获得资金,同时银行负责应收账款的收回。在合同中,保理...

    基于平安并购深发展的案例分析.pptx

    基于平安并购深发展的案例分析.pptx

    [精选]黄金白银延期简介_黄金T+D特点_白银T+D特点_黄金投资历程_深发展银行.pptx

    [精选]黄金白银延期简介_黄金T+D特点_白银T+D特点_黄金投资历程_深发展银行.pptx

    深发展银行开户流程.ppt

    深发展银行开户流程.ppt

    深发展银行开户流程.pptx

    深发展银行开户流程.pptx

    深发展重庆推广提案.ppt

    深发展重庆推广提案.ppt

    深发展重庆推广提案.pptx

    【深发展重庆推广提案】是华宇广告公司针对深圳发展银行重庆分行提出的一份营销策划方案,旨在通过专业且创新的广告策略推动深发展银行在重庆市场的业务拓展。该提案着重强调了以下几个核心知识点: 1. **专业广告...

    科尔尼—深发展银行MoreSlides032905.pptx

    科尔尼—深发展银行MoreSlides032905.pptx

    深发展池融资案例.doc

    深发展银行在调研了众多华东地区的企业后发现,许多中小出口企业与固定的境外买家保持稳定交易,且主要采用赊销方式,尽管账期较长,但收款及时,现金流状况良好。因此,“池融资”产品能针对性地满足这类企业的融资...

    深发展行情看盘软件

    "深发展行情看盘软件"是一款专为投资者设计的金融交易工具,主要针对股票、黄金、货币汇率等金融市场,提供实时、全面的数据分析和图表展示功能。这款软件是看盘者进行市场研究和决策的重要助手。 在股市投资中,...

    平安集团并购深发展案例深度解析.pdf

    【平安集团并购深发展案例深度解析】 平安集团是中国领先的综合性金融服务集团,成立于1988年,总部位于深圳。该集团拥有全方位的金融业务牌照,包括寿险、产险、养老险、健康险、银行、证券、信托、资产管理以及...

Global site tag (gtag.js) - Google Analytics