什么时候应该使用Struct与OpenStruct?


184

通常,与Struct相比,使用OpenStruct的优点和缺点是什么?哪种类型的一般用例适合其中的每一种?


1
我在最近的博客评论“ Structs inside out”中有关于Struct vs. OpenStruct vs. Hash的一些评论,以防万一有人感兴趣。
罗伯特·克莱姆

有关Hash,Struct和OpenStruct的速度的信息已过时。有关最新的基准,请参阅stackoverflow.com/a/43987844/128421
锡人

Answers:


172

使用OpenStruct,您可以任意创建属性。Struct另一方面,创建A时必须定义其属性。一个选择另一个应该主要基于您是否以后需要添加属性。

考虑它们的方法是,一方面是散列,另一方面是类之间​​的频谱的中间地带。它们暗示数据之间的关系比a更为具体Hash,但它们没有实例方法,而类则没有。例如,函数的一堆选项在散列中有意义。它们只是松散相关。函数所需的名称,电子邮件和电话号码可以打包在Struct或中OpenStruct。如果该名称,电子邮件和电话号码需要使用“ First Last”和“ Last,First”两种格式提供名称的方法,则应创建一个类来处理它。


49
“但是它们没有类的实例方法”。好吧,有一个非常普通的模式可以将其用作“正常班级”:class Point < Struct.new(:x, :y); methods here; end
tokland 2011年

10
就今天而言,@tokland,使用方法自定义结构的“首选”方法是将块传递给构造函数Point = Struct.new(:x, :y) { methods here }。(来源)当然, { ... }可以将其写为多行块(do ... end),我认为这是首选方式。
伊万·科尔米切克

1
@IvanKolmychek:太好了,实际上我更喜欢使用块方法。
托克兰

@tokland好。我只是想澄清一下,现在有了一种更好的方法,因为您的评论受到了高度评​​价,所以,刚接触红宝石的人实际上可以认为“好的,所以应该这样做,因为每个人都同意这一点,对吧?:)
Ivan Kolmychek

4
一个问题:当您到达现在要向结构中添加方法时,为什么不使用类呢?
jaydel

82

其他基准:

require 'benchmark'
require 'ostruct'

REP = 100000

User = Struct.new(:name, :age)

USER = "User".freeze
AGE = 21
HASH = {:name => USER, :age => AGE}.freeze

Benchmark.bm 20 do |x|
  x.report 'OpenStruct slow' do
    REP.times do |index|
       OpenStruct.new(:name => "User", :age => 21)
    end
  end

  x.report 'OpenStruct fast' do
    REP.times do |index|
       OpenStruct.new(HASH)
    end
  end

  x.report 'Struct slow' do
    REP.times do |index|
       User.new("User", 21)
    end
  end

  x.report 'Struct fast' do
    REP.times do |index|
       User.new(USER, AGE)
    end
  end
end

对于那些不了解基准结果而又不自己运行它们的不耐烦的人,这里是上面代码的输出(在MB Pro 2.4GHz i7上)

                          user     system      total        real
OpenStruct slow       4.430000   0.250000   4.680000 (  4.683851)
OpenStruct fast       4.380000   0.270000   4.650000 (  4.649809)
Struct slow           0.090000   0.000000   0.090000 (  0.094136)
Struct fast           0.080000   0.000000   0.080000 (  0.078940)

5
使用ruby 2.14时,使用OpenStruct的差异较小,为0.94-0.97,而使用Ostruct(MB Pro 2.2Ghz i7)的差异为0.02-0.03
basex 2015年

1
OpenStruct在速度上等同于使用Struct。请参阅stackoverflow.com/a/43987844/128421
Tin Man

57

更新:

从Ruby 2.4.1开始,OpenStruct和Struct的速度更加接近。参见https://stackoverflow.com/a/43987844/128421

先前:

为了完整性:Struct vs. Class vs. Hash vs. OpenStruct

在Ruby 1.9.2上运行与burtlo类似的代码((4个核中的1个x86_64,8GB RAM)[已编辑以对齐列的表]:

创建1个Mio结构:1.43秒,219 MB / 90MB(virt / res)
创建1个Mio类实例:1.43秒,219 MB / 90MB(virt / res)
创建1个Mio哈希值:4.46秒,493 MB / 364MB(virt / res)
创建1个Mio OpenStructs:415.13秒,2464 MB / 2.3GB(virt / res)#〜比哈希慢100倍
创建100K OpenStructs:10.96秒,369 MB / 242MB(virt / res)

OpenStructs是sloooooow内存密集型,并没有为大型数据集的规模以及

创建1个神达OpenStructs是〜100倍慢于创造1个百万哈希值

start = Time.now

collection = (1..10**6).collect do |i|
  {:name => "User" , :age => 21}
end; 1

stop = Time.now

puts "#{stop - start} seconds elapsed"

对于像我这样的性能成瘾者来说非常有用的信息。谢谢。
Bernardo Oliveira

我指的是Matz对Ruby(MRI)的实现
Tilo 2012年

1
嗨,@ Tilo,您能分享您的代码以得到上述结果吗?我想用它与Hashie :: Mash比较Struct和OStruct。谢谢。
Donny Kurnia 2014年

1
嘿,@ Donny,我刚刚看到了赞誉,并意识到这是在2011年进行的测试-我需要使用Ruby 2.1重新运行它:P不确定我是否有该代码,但复制起来应该很简单。我会尽快修复。
蒂洛

2
从Ruby 2.4.1开始,OpenStruct和Struct的速度更加接近。参见stackoverflow.com/a/43987844/128421
锡人

34

两者的用例完全不同。

您可以将Ruby 1.9中的Struct类视为等同struct于C中的声明。在Ruby中,Ruby Struct.new将一组字段名称作为参数并返回一个新的Class。同样,在C语言中,struct声明采用一组字段,并允许程序员像使用任何内置类型一样使用新的复杂类型。

红宝石:

Newtype = Struct.new(:data1, :data2)
n = Newtype.new

C:

typedef struct {
  int data1;
  char data2;
} newtype;

newtype n;

可以将OpenStruct类与C中的匿名结构声明进行比较。它允许程序员创建复杂类型的实例

红宝石:

o = OpenStruct.new(data1: 0, data2: 0) 
o.data1 = 1
o.data2 = 2

C:

struct {
  int data1;
  char data2;
} o;

o.data1 = 1;
o.data2 = 2;

以下是一些常见的用例。

OpenStructs可用于轻松地将哈希转换为一次性对象,该对象可响应所有哈希键。

h = { a: 1, b: 2 }
o = OpenStruct.new(h)
o.a = 1
o.b = 2

结构对于速写类定义很有用。

class MyClass < Struct.new(:a,:b,:c)
end

m = MyClass.new
m.a = 1

3
这是对它们之间的概念差异的一个很好的答案。感谢您指出了OpenStruct的匿名性,我觉得这样更清楚了。
bryant 2014年

很好的解释!
Yuri Ghensev

24

与Structs相比,OpenStructs使用大量内存,并且性能较慢。

require 'ostruct' 

collection = (1..100000).collect do |index|
   OpenStruct.new(:name => "User", :age => 21)
end

在我的系统上,以下代码在14秒内执行,并消耗了1.5 GB的内存。您的里程可能会有所不同:

User = Struct.new(:name, :age)

collection = (1..100000).collect do |index|
   User.new("User",21)
end

该操作几乎立即完成,并消耗了26.6 MB的内存。


3
但是您知道OpenStruct测试会创建很多临时哈希。我建议稍微修改一下基准-仍然支持您的判决(请参见下文)。
罗伯特·克莱姆

6

Struct

>> s = Struct.new(:a, :b).new(1, 2)
=> #<struct a=1, b=2>
>> s.a
=> 1
>> s.b
=> 2
>> s.c
NoMethodError: undefined method `c` for #<struct a=1, b=2>

OpenStruct

>> require 'ostruct'
=> true
>> os = OpenStruct.new(a: 1, b: 2)
=> #<OpenStruct a=1, b=2>
>> os.a
=> 1
>> os.b
=> 2
>> os.c
=> nil

谢谢你的例子。在实践中对理解很有帮助。
Ahsan


3

使用@Robert代码,我将Hashie :: Mash添加到基准项并获得以下结果:

                           user     system      total        real
Hashie::Mash slow      3.600000   0.000000   3.600000 (  3.755142)
Hashie::Mash fast      3.000000   0.000000   3.000000 (  3.318067)
OpenStruct slow       11.200000   0.010000  11.210000 ( 12.095004)
OpenStruct fast       10.900000   0.000000  10.900000 ( 12.669553)
Struct slow            0.370000   0.000000   0.370000 (  0.470550)
Struct fast            0.140000   0.000000   0.140000 (  0.145161)

您的基准真的很奇怪。我得到了以下结果与ruby2.1.1上I5 MAC:gist.github.com/nicolas-besnard/...
cappie013

好吧,结果会因所使用的红宝石版本和运行它的硬件而异。但是模式仍然是相同的,OpenStruct是最慢的,Struct是最快的。桥江跌在中间。
Donny Kurnia 2014年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.