回到Ruby系列文章
Ruby Struct 如果想要存储带属性的数据(key/value或类似key/value的数据),可采用hash或对象来存储,还可以采用struct结构来存储。Ruby中并没有像C、Golang一样提供真正的Struct数据类型,Ruby是以对象的方式实现Struct的,所有Struct结果都是Struct类的子类且内部类。
Struct和对象的区别 如果使用对象的方式存储属性固定且只需存、取方法(也有其它一些基本方法)的数据,一般需要编写如下代码:
1 2 3 4 5 6 7 8 9 10 class D attr_accessor :name , :age def initialize (name,age ) @name = name @age = age end end d1 = D.new("junmajinlong" , 23 ) d2 = D.new("gaoxiaofang" , 24 )
但是可以更简单:
1 2 3 4 5 D = Struct .new(:name , :age ) d1 = D.new("junmajinlong" , 23 ) d2 = D.new("gaoxiaofang" , 24 ) d2.name = "xiaofang" p d2.name
什么时候使用对象,什么时候使用Struct呢?首先,Struct结构也能当类使用,正如上面的D和普通类没什么区别。如果知道要在小范围内使用,或者是一次性使用的类,那么可以考虑使用Struct。
Struct用法说明 通过Struct.new
可以构造出一个Struct类的子类,这个子类即可当作Struct结构使用,也可当作类来使用。
1 2 3 new([class_name] [, member_name]+) → StructClass new([class_name] [, member_name]+, keyword_init: true) → StructClass new([class_name] [, member_name]+) {|StructClass| block } → StructClass
其中:
因为返回子类,所以可以赋值给一个变量(任意变量名,首字母可小写),此时就有两个名称指向这个类。
1 2 3 4 5 p Struct .new("C" , :name , :age ) a = Struct .new("A" , :name , "age" ) p a == Struct : :A b = Struct .new(:name , :age )
如果keyword_init
设置为true,则表示创建对象实例时,使用key:value
格式参数。
1 2 3 P = Struct .new(:name , :age , keyword_init: true ) p1 = P.new(name: "junmajinlong" , age: 23 ) p p1
如果给定语句块,则可以为Struct子类定义实例方法或其它逻辑:
1 2 3 4 5 6 7 P = Struct .new(:name , :age ) do def who_am_i puts "name is: #{name} " end end p1 = P.new("junmajinlong" , 23 ) p1.who_am_i
建议创建Struct的匿名子类并将其赋值给变量,而不是为其指定一个名称,因为子类都保存在Struct名称空间中,子类名可能会冲突,且匿名子类方便被垃圾回收器回收。
另外,Struct存放的数据的key是固定的,不能随意增加、减少struct成员,因为Struct内部没有定义其它属性的setter/getter方法。
Struct对象的实例方法 Struct对象存取数据很方便,既可以使用对象的点方式,也可以使用数组的数值索引、hash的key索引方式来检索。如果访问不存在的成员或索引越界,则直接报错:
1 2 3 4 5 6 7 8 P = Struct .new(:name , :age ) p1 = P.new("junmajinlong" , 23 ) p p1.name p p1[:name ] p p1["name" ] p p1[0 ] p p1["abc" ]
还有以下几个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 each() 迭代所有成员的value each_pair() 迭代所有成员的key/value members() 返回所有成员的key to_a()或values() 等价,返回所有成员的value values_at() 返回指定成员(需使用数值索引来指定)的value to_h() 返回所有成员的键值对 length()或size() 返回成员个数,即Struct对象有几个属性 == 如果是同一个Struct子类的对象,且value都相等(根据==比较),则true eql? 如果是同一个Struct子类的对象,且value都相等(根据eql?比较),则true filter()或select() 等价,筛选符合条件的成员 Lots = Struct.new(:a, :b, :c, :d, :e, :f) l = Lots.new(11, 22, 33, 44, 55, 66) l.select {|v| v.even? } #=> [22, 44, 66]
Struct和Hash结构的区别
Struct的key是固定不可更改的,无法增、减key,而hash在这方面是自由的
Struct构建语法更简洁,例如Foo = Struct.new(:a,:b,:c)
之后,可以不用指定key的名称直接创建struct对象Foo.new(1,2,3)
默认情况下,Struct访问不存在的元素会报错,而hash是返回nil,尽管可以设置默认值或fetch()抛错
Struct对象在遍历时,顺序是固定可保证的
可为Struct对象提供能访问成员属性的实例方法,而想要为一部分Hash对象单独提供方法(如果只是单个hash对象,可设置单例方法),稍嫌麻烦
1 2 3 4 5 6 7 8 Person = Struct .new(:name , :age ) do def who_am_i puts "name is: #{name} " end end p1 = Person .new("junmajinlong" , 23 ) p1.who_am_i
struct、hash、class和openstruct的对比 Ruby中可以使用这四种方式存储数据,但它们的性能有所区别。下面是创建这四种结构存储数据的性能对比:
参考:https://palexander.posthaven.com/ruby-data-object-comparison-or-why-you-should-never-ever-use-openstruct 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 require 'ostruct' require 'benchmark' COUNT = 10_000_000 NAME = "Test Name" EMAIL = "test@example.org" class Person attr_accessor :name , :email end Benchmark .bm(13 ) do |x | x.report("hash:" ) do COUNT .times do p = {name: NAME , email: EMAIL } end end x.report("struct:" ) do PersonStruct = Struct .new(:name , :email ) COUNT .times do p = PersonStruct .new p.name = NAME p.email = EMAIL end end x.report("class:" ) do COUNT .times do p = Person .new p.name = NAME p.email = EMAIL end end x.report("openstruct:" ) do COUNT .times do p = OpenStruct .new p.name = NAME p.email = EMAIL end end end
测试结果:
1 2 3 4 5 user system total real hash: 0.836551 0.000639 0.837190 ( 0.837230) struct: 1.713467 0.000065 1.713532 ( 1.713540) class: 1.130315 0.000000 1.130315 ( 1.130318) openstruct: 51.333243 0.000012 51.333255 ( 51.333304)
所以,hash最快、class稍慢一点,struct比class又更慢一点,openstruct最慢(非常非常慢,因此不要用它)。