Rubyで文字列の代わりにシンボルを使用するのはいつですか?


98

スクリプトに同じ文字列のインスタンスが2つ以上ある場合は、代わりに記号を使用する必要がありますか?

回答:


175

TL; DR

単純な経験則では、内部識別子が必要になるたびにシンボルを使用します。Ruby <2.2の場合、動的に生成されないシンボルのみを使用して、メモリリークを回避します。

完全な答え

動的に生成される識別子にこれらを使用しない唯一の理由は、メモリの問題です。

多くのプログラミング言語にはシンボルがなく、文字列しかなく、したがって文字列もコードの識別子として使用されるため、この質問は非常に一般的です。シンボルを使用する必要があるときだけでなく、シンボルの意味を心配する必要があります。シンボルは識別子であることを意味します。この理念に従えば、正しいことをする可能性が高くなります。

シンボルと文字列の実装にはいくつかの違いがあります。シンボルについて最も重要なことは、シンボルが不変であることです。つまり、値が変更されることはありません。このため、シンボルは文字列よりもインスタンス化が速く、2つのシンボルの比較などの一部の操作も高速です。

シンボルが不変であるという事実により、Rubyはシンボルを参照するたびに同じオブジェクトを使用でき、メモリを節約できます。そのため、インタプリタが読み取るたびに、:my_key再度インスタンス化する代わりに、メモリから取り出すことができます。これは、毎回新しい文字列を初期化するよりも安価です。

次のコマンドですでにインスタンス化されているすべてのシンボルのリストを取得できますSymbol.all_symbols

symbols_count = Symbol.all_symbols.count # all_symbols is an array with all 
                                         # instantiated symbols. 
a = :one
puts a.object_id
# prints 167778 

a = :two
puts a.object_id
# prints 167858

a = :one
puts a.object_id
# prints 167778 again - the same object_id from the first time!

puts Symbol.all_symbols.count - symbols_count
# prints 2, the two objects we created.

2.2より前のRubyバージョンでは、シンボルがインスタンス化されると、このメモリは二度と解放されません。メモリを解放する唯一の方法は、アプリケーションを再起動することです。そのため、シンボルを誤って使用すると、メモリリークの主な原因にもなります。メモリリークを生成する最も簡単な方法はto_sym、ユーザー入力データに対してメソッドを使用することです。このデータは常に変更されるため、メモリの新しい部分がソフトウェアインスタンスで永久に使用されます。Ruby 2.2は、動的に生成されたシンボルを解放するシンボルガベージコレクターを導入しました。そのため、動的にシンボルを作成することによって生成されるメモリリークは問題ではなくなりました。

あなたの質問に答える:

アプリケーションまたはスクリプトに同じ文字列が2つ以上ある場合、文字列ではなくシンボルを使用する必要があるのは本当ですか?

探しているものがコードで内部的に使用される識別子である場合、シンボルを使用する必要があります。出力を印刷する場合は、2つ以上のオブジェクトがメモリに割り当てられている場合でも、文字列を使用する必要があります。

ここに理由があります:

  1. シンボルは文字列にキャストされるため、文字列の印刷よりもシンボルの印刷が遅くなります。
  2. さまざまなシンボルがたくさんあると、割り当てが解除されることがないため、アプリケーションの全体的なメモリ使用量が増加します。また、コードのすべての文字列を同時に使用することはありません。

@AlanDertの使用例

@AlanDert:hamlコードで%input {type::checkbox}のようなものを何度も使用する場合、チェックボックスとして何を使用する必要がありますか?

私:はい。

@AlanDert:しかし、htmlページにシンボルを印刷するには、文字列に変換する必要がありますね。それを使用する意味は何ですか?

入力のタイプは何ですか?使用したい入力の種類の識別子、またはユーザーに表示したい何か。

ある時点でHTMLコードになることは事実ですが、コードのその行を記述している現在、これは識別子であることを意味します。これは、必要な入力フィールドの種類を識別します。したがって、コードで何度も使用され、識別子と常に同じ「文字列」の文字を持ち、メモリリークを生成しません。

とはいえ、データを評価して文字列の方が速いかどうかを確認してみませんか?

これは私がこれのために作成した簡単なベンチマークです:

require 'benchmark'
require 'haml'

str = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: "checkbox"}').render
  end
end.total

sym = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: :checkbox}').render
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s

3つの出力:

# first time
String: 5.14
Symbol: 5.07
#second
String: 5.29
Symbol: 5.050000000000001
#third
String: 4.7700000000000005
Symbol: 4.68

したがって、smbolを使用する方が実際には文字列を使用するよりも少し高速です。何故ですか?HAMLの実装方法によって異なります。確認するにはHAMLコードを少しハックする必要がありますが、識別子の概念でシンボルを使い続けると、アプリケーションの速度と信頼性が向上します。質問が発生した場合は、ベンチマークして回答を取得します。


@andrewcockerham提供されたリンクが機能していません(エラー404)。最後の/(の後strings)をリンクから削除する必要があります。ここにあります:www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ symbols-and-strings
Atul Khanduri

14

簡単に言えば、シンボルは文字で構成される名前ですが、不変です。逆に、文字列は文字の順序付きコンテナであり、その内容を変更することができます。


4
+1。シンボルと文字列はまったく別のものです。それらがひどく教えられていない限り(つまり、「シンボルは単なる不変の文字列である」という誤解がない限り)どちらを使用するかについての混乱はまったくありません。
イェルクWミッターク

@JörgWMittag:その通りです。
ボリスStitnicky 2013年

5
あなたはポイントを持っていますが、作られた質問に答えないでください。OPは、それが十分なことを教えていない、シンボルと文字列を混乱さは異なるものである-あなたは、彼らが同様としているかを理解するために彼を助ける必要があります彼らが異なっている
fotanus

1
@JörgWMittagはWeb全体で発生しているようですが、ドキュメントを調べたり、幸運なことに、説明どおりに説明したりする人を探している場合を除きます。
sargas 14年

8
  1. RubyシンボルはO(1)比較のオブジェクト

2つの文字列を比較するには、すべての文字を調べる必要がある可能性があります。長さNの2つの文字列の場合、これにはN + 1の比較が必要になります(コンピューター科学者は "O(N)時間"と呼びます)。

def string_comp str1, str2
  return false if str1.length != str2.length
  for i in 0...str1.length
    return false if str1[i] != str2[i]
  end
  return true
end
string_comp "foo", "foo"

しかし、:fooのすべての出現は同じオブジェクトを参照しているため、オブジェクトIDを調べることでシンボルを比較できます。これは1回の比較で実行できます(コンピューター科学者は「O(1)時間」と呼んでいます)。

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Rubyシンボルは、自由形式の列挙のラベルです

C ++では、「列挙」を使用して、関連する定数のファミリーを表すことができます。

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;

しかし、Rubyは動的言語であるため、BugStatus型を宣言したり、正当な値を追跡したりする必要はありません。代わりに、列挙値をシンボルとして表します。

original_status = :open
current_status  = :closed

3.Rubyシンボルは一定の一意の名前です

Rubyでは、文字列の内容を変更できます。

"foo"[0] = ?b # "boo"

ただし、シンボルの内容は変更できません。

:foo[0]  = ?b # Raises an error
  1. Rubyシンボルはキーワード引数のキーワードです

Ruby関数にキーワード引数を渡すときは、記号を使用してキーワードを指定します。

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Rubyシンボルはハッシュキーに最適です

通常、ハッシュテーブルのキーを表すために記号を使用します。

options = {}
options[:auto_save]     = true
options[:show_comments] = false

5

ここに私がcodecademyで見つけた素晴らしい文字列対シンボルのベンチマークがあります:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  1000_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  1000_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

出力は次のとおりです。

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.

2
これが10分の1秒であるという事実を見失わないようにしましょう。
ケーシー

それはすべて相対的です。時々百分の問題。
Yurii

2
100万回以上の100分の1秒の反復?それがあなたに利用できる最高の最適化であるならば、あなたのプログラムはすでにかなり十分に最適化されていると思います。
ケーシー

0
  • ハッシュキー識別子として記号を使用する

    {key: "value"}

  • シンボルを使用すると、メソッドを別の順序で呼び出すことができます

     def write(file :, data :, mode: "ascii")
          #簡潔にするために削除
     終わり
     書き込み(データ:123、ファイル: "test.txt")
  • 文字列として保持し、メモリを節約するためにフリーズ

    label = 'My Label'.freeze

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.