分享 Ruby | Rails - 浅拷贝 | 深拷贝

hiveer · 2014年10月21日 · 最后由 EricWJP 回复于 2017年03月21日 · 9470 次阅读
本帖已被管理员设置为精华贴

大家觉得标题的写法如何? 我这样写标题只有一个目的,在表达字面意思的同时体现出他们之间的组合情况。

准备在开讲前 什么是浅拷贝?什么是深拷贝?

拷贝的目的是为了得到两个看起来一样但是本质上又不同的对象,这里的本质体现在他们是否指向了同一个存储空间。

在 Ruby 中,对象的定义是一组实例变量外加一个指向其类的引用。如果其某个实例变量又依赖于另外的对象,那么这个时候我们如何来对待这个依赖的对象呢?

根据我们处理方式的不同将会得到两种不同的拷贝概念 浅拷贝:对所有的依赖对象不做拷贝,仅仅是引用 深拷贝:对所有的依赖(依赖的依赖)对象都做拷贝

第一种组合情况:Ruby - 浅拷贝 在 Ruby 中有两个方法来实现浅拷贝clone, dup,他们都是Object mix in Kernel得到的方法。 他们之间有个微小的差别,dup将不会对 extended 的 modules 进行拷贝。

第二种组合情况:Ruby - 深拷贝 Ruby 并没有一个现成的方法来实现对象的深拷贝,目前一个常用的方法是通过Marshal来实现 Marshal.load(Marshal.dump(obj))

第三种组合情况:Rails - 浅拷贝 Rails 并没有对 Ruby 的浅拷贝做什么的扩展

第四种组合情况:Rails - 深拷贝 说到这里顺便就吐槽一下 Rails 官方指导文档的描述真心不够贴切(大家可以对比第二个链接中的源代码): http://guides.rubyonrails.org/active_support_core_extensions.html#deep-duplicating

ActiveSupport 中 deep_dup 源码 https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/deep_dup.rb

从源码我们可以看到,Rails 对数组和 Hash 做了深拷贝的扩展,而对 Object 的扩展没有任何实际意义,仅仅是辅助于数组和 Hash 的扩展。也就是说,加载了 Rails 之后,我们可以很方便的对数组和 Hash 做深拷贝。但是对于普通对象而言,依然无能为力。 我们看看官方指导文档的描述:

2.4 deep_dup The deep_dup method returns deep copy of a given object. Normally, when you dup an object that contains other objects, Ruby **does not dup them, so it creates a shallow copy of the object.

看后是否觉得,Yes,good job! 但是,实际上它并没有真的做到。 那么针对这样的情况,我们不得不把 Ruby - 深拷贝 中提到的深拷贝方法搬过来继续用!

下面看一个普通对象的拷贝例子: 源码出自: http://alancohen.tumblr.com/post/35030966381/shallow-vs-deep-copy

class Obj
  attr_accessor :first, :second
  def initialize
    @first = {:one => 'x',:two => 'y',:three => 'z'}
    @second = {[1,2]=>'x',[3,2]=>'o'}
  end
end

# 浅拷贝
obj1 = Obj.new
obj2 = obj1.clone

obj1.object_id != obj2.object_id 
# 拷贝对象和源对象都指向同样的依赖
obj1.first.object_id == obj2.first.object_id

# 深拷贝
obj3 = Marshal.load( Marshal.dump(obj1) )
# 依赖也被拷贝了
obj3.first.object_id != obj1.first.object_id

每帖必自顶!

应该多一些这样的技术贴,少走很多弯路。 @hiveer 帮看一下https://ruby-china.org/topics/22166,技术救助。

@jay_li 晚点看哈,你的帖子有点长

感觉不错哦

@cod7ce @imlcl 就是总结了下,谢谢大牛们的力挺。

ruby 的 dupclone还有一个不同是 clone 会保存 object 的 state。 Copies the frozen and tainted state of obj.

拷贝还有一个 initialize_copy method 可以了解一下,相关文章 http://www.jonathanleighton.com/articles/2011/initialize_clone-initialize_dup-and-initialize_copy-in-ruby/

9 楼 已删除

clone copies the singleton class (if any), whereas dup does not

@altkatz 这个链接很有用!:)

#8 楼 @cantin 实际上dup, clone都会 copy "frozen", "tainted" 这两个状态!

[1] pry(main)> hash = {:one => 1, :two => 2}
=> {:one=>1, :two=>2}
[2] pry(main)> hash.freeze
=> {:one=>1, :two=>2}
[4] pry(main)> hash.frozen?
=> true
[5] pry(main)> hash2 = hash.dup
=> {:one=>1, :two=>2}
[6] pry(main)> hash3 = hash.clone
=> {:one=>1, :two=>2}
[7] pry(main)> hash.frozen?
=> true
[8] pry(main)> hash3.frozen?
=> true

#10 楼 @gaodu_2014 赞! In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance. 这是文档上一句话,这证实了你的说法。dup实际是根据class来新建一个兄弟对象,而clone完全的复印一分。

#12 楼 @hiveer

[7] pry(main)> hash.frozen? => true

应该是 hash2.forzen 吧。

#14 楼 @cantin 非常抱歉,没有仔细检查。我重新测试了一遍,结果是对于 freeze 状态只有 clone 会拷贝,而 taint 状态的话,dup 和 clone 都会进行拷贝。

#15 楼 @hiveer taint 的确会被拷贝。

我只看了clone的文档,没看dup的。其实文档里面都有提到。

dup copies the tainted state of obj. http://ruby-doc.org/core-2.1.3/Object.html#method-i-dup

@hiveer 今天遇到了这个问题,有何解释?

irb(main):001:0> arr_a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> arr_b = arr_a.clone
=> [1, 2, 3]
irb(main):003:0> arr_a.object_id == arr_b.object_id
=> false
irb(main):004:0> arr_b[0] = 4
=> 4
irb(main):005:0> arr_b
=> [4, 2, 3]
irb(main):006:0> arr_a
=> [1, 2, 3]
irb(main):007:0> arr_a = [[1,2,3],[4,5,6]]
=> [[1, 2, 3], [4, 5, 6]]
irb(main):008:0> arr_b = arr_a.clone
=> [[1, 2, 3], [4, 5, 6]]
irb(main):009:0> arr_a.object_id == arr_b.object_id
=> false
irb(main):010:0> arr_b[0][0] = 7
=> 7
irb(main):011:0> arr_b
=> [[7, 2, 3], [4, 5, 6]]
irb(main):012:0> arr_a
=> [[7, 2, 3], [4, 5, 6]]

一位数组在 clone 时候改变新的对象没有对原来数组产生影响,但是二维数组用同样的方法居然会改变原来的数组?

@ailen 首先你进行的操作是个浅拷贝,其次二位数组可以理解为外层数据依赖于内层的数组,所以如果你需要拷贝内层数据就需要深拷贝。你可以尝试文档中的 Ruby 深拷贝方法

非常不错 👲

需要 登录 后方可回复, 如果你还没有账号请 注册新账号