Ruby

RubyのStringとSymbolの違いとObject#freezeについて

More than 3 years have passed since last update.

ruby 2.3.0p0 (2015-12-25 revision 53290)で検証している。


StringとSymbolの違い

Stringクラスは文字列毎に新しくインスタンスを生成するが、Symbolクラスは同じ文字列の場合はインスタンスを生成しない。


検証する


Object#object_idを使って各オブジェクトを判別する

a = "test"

b = "test"

puts a.object_id # 70319641612700
puts b.object_id # 70319654222120

c = :test
d = :test

puts c.object_id # 356188
puts d.object_id # 356188


Object#equal?を使ってオブジェクトが同一であるか判別する

Object#equal?は、引数として与えられたオブジェクトがレシーバとなるオブジェクトと同じオブジェクトかどうかを判断する。

'test'.equal? 'test' # false

:test.equal? :test # true


使い分け

ハッシュのキー等、何度も同じ文字を扱う場合は余分なインスタンスが生成されないのでSymbolを扱ったほうが若干早い。


いっぱい生成してみた

require 'benchmark'

Benchmark.bm 10 do |r|

r.report 'symbol' do
100000000.times { :test }
end

r.report 'string' do
100000000.times { 'test' }
end
end

結果はこんな感じ。

                 user     system      total        real

symbol 5.790000 0.040000 5.830000 ( 5.885552)
string 8.970000 0.080000 9.050000 ( 9.183865)

と、前述の通りsymbolの方がちょっとだけ早い。

何度か試してみたが誤差は大体±0.5秒程だった。


StringにObject#freezeを使った場合

freezeメソッドをつかってオブジェクトの状態を変更した時のオブジェクトIDはどうなるのか

'str'.freeze.equal? 'str'.freeze # true

a = 'str'
a.freeze.equal? a # true
b = 'str'
b.equal? b.freeze # true

c = 'str'
d = 'str'
c.freeze
d.freeze
c.frozen? # true
d.frozen? # true
c.equal? d # false

新たに生成したオブジェクトに対してfreezeを使うと毎回文字列オブジェクトを生成せず、常に同じオブジェクトを返している。

オーバーヘッドをちょっとでも小さくするためには文字列リテラルにfreezeをつけると良い。

ちなみに、Ruby2.3.0以降はfrozen_string_literal: trueというマジックコメントを入れることでデフォルトでStringオブジェクトはfreezeとなる。

# frozen_string_literal: true

"str".frozen? # => true