何时在Ruby中使用Struct而不是Hash?

82

我没有太多的编程经验,但对我来说,Struct似乎与Hash有些相似。

  • Struct能做什么?
  • Struct有什么功能是Hash无法做到的?

通过谷歌搜索,我了解到在C语言中,Struct的概念非常重要,但是我对C语言知之甚少。

5个回答

111

结构体与哈希映射的区别如下(除代码外):

  • 结构体具有固定的属性集,而哈希映射可以添加新的键。
  • 在结构体示例上调用不存在的属性将导致NoMethodError,而从哈希映射获取不存在的键的值只会返回nil。
  • 即使结构体具有相同的属性且实例具有相同的值,不同结构体的两个实例也永远不会相等(即 Struct.new(:x).new(42) == Struct.new(:x).new(42) 返回false,而 Foo = Struct.new(:x); Foo.new(42)==Foo.new(42) 返回true)。
  • 结构体的to_a方法返回值数组,而哈希映射的to_a方法返回键值对的数组(其中“pair”表示“双元素数组”)
  • 如果 Foo = Struct.new(:x, :y, :z),则可以使用Foo.new(1,2,3)创建一个Foo实例,而不必拼写属性名称。

因此,回答问题:当您想要建模具有已知属性集的对象时,请使用结构体。当您想要建模任意对象时,请使用哈希映射(例如,统计字符串中每个单词出现的次数或将昵称映射到全名等绝对不适合结构体,而使用结构体可以很好地建模具有名称、年龄和地址的人)。

顺便说一下:C语言中的结构体与Ruby中的结构体几乎没有关系,所以不要让自己被这些搞混了。


你提到的其他观点都是正确的(因此+1),但是 Struct#== 的工作方式与您解释的不同,当您将 Struct.new 的结果存储为变量时,与使用相同参数两次调用它时也会不同。 - Mark Rushakoff
我理解它们之间的区别,但使用结构体相比哈希表有什么真正的优势呢?毕竟哈希表可以完成同样的事情,并且更简单易用。似乎结构体有点多余。 - rcd
结构体的 to_a 方法是否保证始终返回与初始定义的结构体完全相同顺序的值数组?例如,给定 Foo = Struct.new(:z, :a, :b),我可以依赖于 Foo.new(1, 2, 3).to_a[0] == 1 吗?具体来说,我可以依赖于 to_a[0] 始终是 :z 的值吗? - igrek
1
@igrek 我知道这是一个老问题,但答案是肯定的。数组的顺序与您传递的属性名称的顺序相同。 - 3limin4t0r
@sepp2k,你的第二点并不完全正确。您可以使用方括号方式访问属性。如果 struct = Struct.new(:name, :age) 那么我仍然可以通过 struct[variable] 访问属性。 - 3limin4t0r
显示剩余3条评论

52

我知道这个问题差不多已经有很好的答案了,但令人惊讶的是没有人谈论过Struct的最大区别和真正的优势。我猜这就是为什么有人仍在问的原因。

我理解它们之间的区别,但相比Hash,使用Struct的真正优势是什么?Hash也能做到同样的事情,并且更简单易用。看起来Struct似乎有些多余。

Struct

require 'benchmark'

Benchmark.bm 10 do |bench|
  bench.report "Hash: " do
    50_000_000.times do { name: "John Smith", age: 45 } end
  end

  bench.report "Struct: " do
    klass = Struct.new(:name, :age)
    50_000_000.times do klass.new("John Smith", 45) end
  end

end

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x64-mingw32].
#                 user     system      total        real
# Hash:       22.340000   0.016000  22.356000 ( 24.260674)
# Struct:     12.979000   0.000000  12.979000 ( 14.095455)

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin11.0]
# 
#                  user     system      total        real
# Hash:       31.980000   0.060000  32.040000 ( 32.039914)
# Struct:     16.880000   0.010000  16.890000 ( 16.886061)

3
也许这很有趣:我重新运行了你的基准测试,使用Cygwin Ruby(2.2.3p173)进行一次测试,得到哈希表的用户时间为62.462,结构体的用户时间为19.875。然后我使用JRuby 1.7.23在JVM 1.7上重新运行了测试,哈希表的时间为8.401,结构体的时间为8.701。虽然在Ruby中速度优势很大,但在JRuby下两者似乎速度相同。 - user1934428
我使用更新的 CRuby 版本(2.5)重新运行了这个简单的基准测试,而 Struct 仍然更快。虽然不像以前那样快,但仍然显著快速(在我的机器上为12.19秒对比8.28秒)。 - Shimu
在我的 MacBook 上,Ruby 2.6.6 的 Hash(5.9秒)比 Struct(9.4秒)更快。而在 Ruby 2.3.1 中,Struct 比 hash 快3倍。 - Kirill
使用 ruby 3.2.0 (2022-12-25 revision a528908271) [x86_64-linux],我得到了相反的结果。哈希表:6.5 / 结构体:10.8 - Rein Avila

16

另一个主要区别是您可以向结构添加行为方法。

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello #{name}!"
  end
end

Customer.new("Dave", "123 Main").greeting  # => "Hello Dave!"

2
我认为这是Struct和Hash之间的一个巨大区别,可以证明在Rails规范中,你不应该在同一个Ruby文件中拥有两个类(因为自动加载问题)。因此,很多时候使用Struct是创建作为Presenter/Decorator的类替代品的好方法。 - sandre89

12

Struct文档中可知:

结构体是一种方便的方式,可以使用访问器方法将多个属性捆绑在一起,而不必编写显式类。

另一方面,Hash

哈希是键值对的集合。它类似于数组,但索引是通过任何对象类型的任意键进行的,而不是整数索引。通过键或值遍历哈希的顺序似乎是任意的,并且通常不会按照插入顺序排序。

主要区别在于如何访问数据。

ruby-1.9.1-p378 > Point = Struct.new(:x, :y)
 => Point 
ruby-1.9.1-p378 > p = Point.new(4,5)
 => #<struct Point x=4, y=5> 
ruby-1.9.1-p378 > p.x
 => 4 
ruby-1.9.1-p378 > p.y
 => 5 
ruby-1.9.1-p378 > p = {:x => 4, :y => 5}
 => {:x=>4, :y=>5} 
ruby-1.9.1-p378 > p.x
NoMethodError: undefined method `x' for {:x=>4, :y=>5}:Hash
    from (irb):7
    from /Users/mr/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `<main>'
ruby-1.9.1-p378 > p[:x]
 => 4 
ruby-1.9.1-p378 > p[:y]
 => 5 

简而言之,当您需要一个类是"纯数据结构"时(可选择将其扩展为更多方法),您会创建一个新的Struct;而当您根本不需要正式类型时,您会使用Hash。

0

如果你只是要封装数据,那么哈希表(或哈希表数组)就可以了。如果你计划让数据与其他数据进行操作或交互,那么结构体可以打开一些有趣的可能性:

Point = Struct.new(:x, :y)
point_a = Point.new(0,0)
point_b = Point.new(2,3)

class Point
  def distance_to another_point
    Math.sqrt((self.x - another_point.x)**2 + (self.y - another_point.y)**2)
  end
end

puts point_a.distance_to point_b

你可以使用 class Point << Hash,这样所有的东西都会有点类似。 - Nakilon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接