在 Ruby 中深度复制对象最有效的方法是什么?
我知道序列化对象(据我所知)是有效深度复制对象的唯一方法(只要它不像 IO 之类的有状态),但这是一种特别多的方法比另一个更有效率?
例如,由于我使用的是 Rails,所以我总是可以使用 ActiveSupport::JSON、to_xml - 据我所知,编组对象是最受接受的方法之一方法来做到这一点。我预计编组可能是其中最有效的,因为它是 Ruby 内部的,但我是否遗漏了什么?
编辑:请注意,它的实现是我已经介绍过的 - 我不想替换现有的浅复制方法(例如 dup
和 clone
),所以我最终可能会添加 Object::deep_copy
,其结果是上述方法(或您有的任何建议:)中开销最小的方法。
I know that serializing an object is (to my knowledge) the only way to effectively deep-copy an object (as long as it isn't stateful like IO
and whatnot), but is one way particularly more efficient than another?
For example, since I'm using Rails, I could always use ActiveSupport::JSON
, to_xml
- and from what I can tell marshalling the object is one of the most accepted ways to do this. I'd expect that marshalling is probably the most efficient of these since it's a Ruby internal, but am I missing anything?
Edit: note that its implementation is something I already have covered - I don't want to replace existing shallow copy methods (like dup
and clone
), so I'll just end up likely adding Object::deep_copy
, the result of which being whichever of the above methods (or any suggestions you have :) that has the least overhead.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我也想知道同样的事情,所以我对几种不同的技术进行了相互比较。我主要关心数组和哈希 - 我没有测试任何复杂的对象。也许毫不奇怪,定制的深度克隆实现被证明是最快的。如果您正在寻求快速、轻松的实施,Marshal 似乎是您的最佳选择。
我还使用 Rails 3.0.7 对 XML 解决方案进行了基准测试,如下所示。它慢得多,慢得多,大约 10 秒,仅进行 1000 次迭代(下面的解决方案都在基准测试中运行了 10,000 次)。
关于我的 JSON 解决方案的两个注释。首先,我使用了 C 变体,版本 1.4.3。其次,它实际上并不能 100% 工作,因为符号将被转换为字符串。
这一切都是用 ruby 1.9.2p180 运行的。
结果是:
I was wondering the same thing, so I benchmarked a few different techniques against each other. I was primarily concerned with Arrays and Hashes - I didn't test any complex objects. Perhaps unsurprisingly, a custom deep-clone implementation proved to be the fastest. If you are looking for quick and easy implementation, Marshal appears to be the way to go.
I also benchmarked an XML solution with Rails 3.0.7, not shown below. It was much, much slower, ~10 seconds for only 1000 iterations (the solutions below all ran 10,000 times for the benchmark).
Two notes regarding my JSON solution. First, I used the C variant, version 1.4.3. Second, it doesn't actually work 100%, as symbols will be converted to Strings.
This was all run with ruby 1.9.2p180.
results in:
我认为您需要向您正在复制的类添加一个initialize_copy方法。然后将深拷贝的逻辑放在那里。然后,当您调用克隆时,它将触发该方法。我没做过,但这是我的理解。
我认为 B 计划只是覆盖克隆方法:
输出
我相信你可以通过一些修补让它变得更酷,但无论好坏,这可能就是我会做的。
I think you need to add an initialize_copy method to the class you are copying. Then put the logic for the deep copy in there. Then when you call clone it will fire that method. I haven't done it but that's my understanding.
I think plan B would be just overriding the clone method:
Output
I'm sure you could make that cooler with a little tinkering but for better or for worse that is probably how I would do it.
Ruby 不包含深层克隆的原因可能与问题的复杂性有关。请参阅最后的注释。
要制作将“深度复制”、哈希、数组和元素值的克隆,即复制原始文件中的每个元素,以便副本将具有相同的值,但有新的对象,您可以使用以下命令
:如果你想重新定义 Ruby 的
clone
方法的行为,你可以将其命名为clone
而不是deepclone
(在 3 个地方),但我有不知道重新定义 Ruby 的克隆行为将如何影响 Ruby 库或 Ruby on Rails,所以请自负。就我个人而言,我不建议这样做。例如:
如果您希望您的类正确地深度克隆,它们的
new
方法(初始化)必须能够以标准方式深度克隆该类的对象,即,如果给出第一个参数后,假定它是要深度克隆的对象。例如,假设我们想要一个 M 类。第一个参数必须是 M 类的可选对象。这里我们有第二个可选参数 z 来预先设置新对象中 z 的值。
此处克隆期间将忽略
z
预设,但您的方法可能有不同的行为。此类的对象将如下创建:其中类 M 的对象是数组的一部分:
注意:
deepclone
尝试克隆一个不以标准方式克隆自身的对象,它可能会失败。deepclone
不会深度复制哈希中的键。原因是它们通常不被视为数据,但如果将hash[k]
更改为hash[k.deepclone]
,它们也将被深度复制。Probably the reason Ruby doesn't contain a deep clone has to do with the complexity of the problem. See the notes at the end.
To make a clone that will "deep copy," Hashes, Arrays, and elemental values, i.e., make a copy of each element in the original such that the copy will have the same values, but new objects, you can use this:
If you want to redefine the behavior of Ruby's
clone
method , you can name it justclone
instead ofdeepclone
(in 3 places), but I have no idea how redefining Ruby's clone behavior will affect Ruby libraries, or Ruby on Rails, so Caveat Emptor. Personally, I can't recommend doing that.For example:
If you want your classes to deepclone properly, their
new
method (initialize) must be able to deepclone an object of that class in the standard way, i.e., if the first parameter is given, it's assumed to be an object to be deepcloned.Suppose we want a class M, for example. The first parameter must be an optional object of class M. Here we have a second optional argument
z
to pre-set the value of z in the new object.The
z
pre-set is ignored during cloning here, but your method may have a different behavior. Objects of this class would be created like this:Where objects of class M are part of an array:
Notes:
deepclone
tries to clone an object which doesn't clone itself in the standard way, it may fail.deepclone
tries to clone an object which can clone itself in the standard way, and if it is a complex structure, it may (and probably will) make a shallow clone of itself.deepclone
doesn't deep copy the keys in the Hashes. The reason is that they are not usually treated as data, but if you changehash[k]
tohash[k.deepclone]
they will also be deep copied also.new
method, such as Fixnum. These objects always have the same object ID, and are copied, not cloned.