Wann sollten in Ruby Symbole anstelle von Zeichenfolgen verwendet werden?

97

Wenn mein Skript mindestens zwei Instanzen derselben Zeichenfolge enthält, sollte ich stattdessen ein Symbol verwenden?

Alan Coromano
quelle

Antworten:

174

TL; DR

Eine einfache Faustregel lautet, jedes Mal Symbole zu verwenden, wenn Sie interne Bezeichner benötigen. Verwenden Sie für Ruby <2.2 Symbole nur, wenn sie nicht dynamisch generiert werden, um Speicherverluste zu vermeiden.

Vollständige Antwort

Der einzige Grund, sie nicht für Bezeichner zu verwenden, die dynamisch generiert werden, sind Speicherprobleme.

Diese Frage ist sehr häufig, da viele Programmiersprachen keine Symbole haben, sondern nur Zeichenfolgen, und daher werden Zeichenfolgen auch als Bezeichner in Ihrem Code verwendet. Sie sollten sich Gedanken darüber machen, was Symbole sein sollen , nicht nur, wenn Sie Symbole verwenden sollten . Symbole sollen Bezeichner sein. Wenn Sie dieser Philosophie folgen, besteht die Möglichkeit, dass Sie die Dinge richtig machen.

Es gibt verschiedene Unterschiede zwischen der Implementierung von Symbolen und Zeichenfolgen. Das Wichtigste an Symbolen ist, dass sie unveränderlich sind . Dies bedeutet, dass sich ihr Wert niemals ändern wird. Aus diesem Grund werden Symbole schneller instanziiert als Zeichenfolgen, und einige Operationen wie das Vergleichen zweier Symbole sind ebenfalls schneller.

Die Tatsache, dass ein Symbol unveränderlich ist, ermöglicht es Ruby, jedes Mal, wenn Sie auf das Symbol verweisen, dasselbe Objekt zu verwenden, wodurch Speicherplatz gespart wird. Jedes Mal, wenn der Interpreter liest :my_key, kann er es aus dem Speicher entnehmen, anstatt es erneut zu instanziieren. Dies ist kostengünstiger als jedes Mal eine neue Zeichenfolge zu initialisieren.

Sie können eine Liste aller Symbole abrufen, die bereits mit dem Befehl instanziiert wurden 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.

Bei Ruby-Versionen vor 2.2 ist dieser Speicher nach der Instanziierung eines Symbols nie wieder frei . Die einzige Möglichkeit, den Speicher freizugeben, besteht darin, die Anwendung neu zu starten. Symbole sind daher auch eine Hauptursache für Speicherverluste, wenn sie falsch verwendet werden. Der einfachste Weg, einen Speicherverlust zu erzeugen, ist die Verwendung der Methode to_symfür Benutzereingabedaten, da sich diese Daten immer ändern und ein neuer Teil des Speichers für immer in der Softwareinstanz verwendet wird. Ruby 2.2 hat den Symbol-Garbage-Collector eingeführt , der dynamisch generierte Symbole freigibt, sodass die durch dynamisches Erstellen von Symbolen erzeugten Speicherlecks kein Problem mehr darstellen.

Beantwortung Ihrer Frage:

Stimmt es, dass ich anstelle einer Zeichenfolge ein Symbol verwenden muss, wenn meine Anwendung oder mein Skript mindestens zwei gleiche Zeichenfolgen enthält?

Wenn Sie nach einer Kennung suchen, die intern in Ihrem Code verwendet werden soll, sollten Sie Symbole verwenden. Wenn Sie eine Ausgabe drucken, sollten Sie Zeichenfolgen verwenden, auch wenn diese mehrmals angezeigt werden, und sogar zwei verschiedene Objekte im Speicher zuweisen.

Hier ist die Begründung:

  1. Das Drucken der Symbole ist langsamer als das Drucken von Zeichenfolgen, da sie in Zeichenfolgen umgewandelt werden.
  2. Wenn Sie viele verschiedene Symbole haben, erhöht sich die Gesamtspeicherauslastung Ihrer Anwendung, da diese niemals freigegeben werden. Und Sie verwenden niemals alle Zeichenfolgen aus Ihrem Code gleichzeitig.

Anwendungsfall von @AlanDert

@AlanDert: Wenn ich im haml-Code mehrmals etwas wie% input {type :: checkbox} verwende, was soll ich dann als Kontrollkästchen verwenden?

Ich schon.

@AlanDert: Aber um ein Symbol auf einer HTML-Seite auszudrucken, sollte es in einen String konvertiert werden, nicht wahr? Was bringt es dann, es zu benutzen?

Was ist die Art einer Eingabe? Eine Kennung der Art der Eingabe, die Sie verwenden möchten, oder etwas, das Sie dem Benutzer anzeigen möchten?

Es ist wahr, dass es irgendwann zu HTML-Code wird, aber im Moment, in dem Sie diese Zeile Ihres Codes schreiben, ist es gemeint, ein Bezeichner zu sein - es gibt an, welche Art von Eingabefeld Sie benötigen. Daher wird es in Ihrem Code immer wieder verwendet und hat immer die gleiche "Zeichenfolge" wie der Bezeichner und erzeugt keinen Speicherverlust.

Warum werten wir die Daten nicht aus, um festzustellen, ob die Zeichenfolgen schneller sind?

Dies ist ein einfacher Benchmark, den ich dafür erstellt habe:

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

Drei Ausgänge:

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

Die Verwendung von Smbols ist also etwas schneller als die Verwendung von Strings. Warum ist das so? Dies hängt davon ab, wie HAML implementiert ist. Ich müsste ein bisschen HAML-Code hacken, um zu sehen, aber wenn Sie weiterhin Symbole im Konzept eines Bezeichners verwenden, wird Ihre Anwendung schneller und zuverlässiger. Wenn Fragen auftauchen, vergleichen Sie sie und erhalten Sie Ihre Antworten.

fotanus
quelle
@andrewcockerham Ihr angegebener Link funktioniert nicht (Fehler-404). Sie müssen das letzte /(nach strings) aus dem Link entfernen . Hier ist es: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ Symbole-und-Zeichenfolgen
Atul Khanduri
14

Einfach ausgedrückt ist ein Symbol ein Name, der aus Zeichen besteht, aber unveränderlich ist. Eine Zeichenfolge ist dagegen ein geordneter Container für Zeichen, deren Inhalt sich ändern darf.

Boris Stitnicky
quelle
4
+1. Symbole und Zeichenfolgen sind völlig verschiedene Dinge. Es gibt wirklich keine Verwirrung darüber, welche man verwenden soll, es sei denn, sie wurden schlecht unterrichtet (dh der Irrtum "Ein Symbol ist nur eine unveränderliche Zeichenfolge").
Jörg W Mittag
@ JörgWMittag: Genau.
Boris Stitnicky
5
Sie haben einen Punkt, beantworten jedoch nicht die gestellte Frage. Das OP verwechselt Zeichenfolgen mit Symbolen, es reicht nicht aus, zu sagen, dass es sich um verschiedene Dinge handelt - Sie sollten ihm helfen, zu verstehen, was sie gleich sind und worin sie sich unterscheiden
fotanus
1
@ JörgWMittag, das anscheinend im gesamten Web stattfindet, es sei denn, Sie schauen in die Dokumentation oder haben das Glück, Leute zu finden, die die Dinge so erklären möchten, wie sie wirklich sind.
Sargas
8
  1. Ein Ruby-Symbol ist ein Objekt mit O (1) -Vergleich

Um zwei Zeichenfolgen zu vergleichen, müssen wir möglicherweise jedes Zeichen betrachten. Für zwei Zeichenfolgen der Länge N sind N + 1-Vergleiche erforderlich (die von Informatikern als "O (N) -Zeit" bezeichnet werden).

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"

Da sich jedes Erscheinungsbild von: foo auf dasselbe Objekt bezieht, können wir Symbole vergleichen, indem wir uns die Objekt-IDs ansehen. Wir können dies mit einem einzigen Vergleich tun (den Informatiker als "O (1) -Zeit" bezeichnen).

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Ein Ruby-Symbol ist eine Bezeichnung in einer Freiform-Aufzählung

In C ++ können wir "Aufzählungen" verwenden, um Familien verwandter Konstanten darzustellen:

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

Da Ruby eine dynamische Sprache ist, müssen wir uns nicht darum kümmern, einen BugStatus-Typ zu deklarieren oder die gesetzlichen Werte zu verfolgen. Stattdessen stellen wir die Aufzählungswerte als Symbole dar:

original_status = :open
current_status  = :closed

3. Ein Rubinsymbol ist ein konstanter, eindeutiger Name

In Ruby können wir den Inhalt einer Zeichenfolge ändern:

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

Wir können den Inhalt eines Symbols jedoch nicht ändern:

:foo[0]  = ?b # Raises an error
  1. Ein Ruby-Symbol ist das Schlüsselwort für ein Schlüsselwortargument

Wenn Sie Schlüsselwortargumente an eine Ruby-Funktion übergeben, geben Sie die Schlüsselwörter mithilfe von Symbolen an:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Ein Ruby-Symbol ist eine ausgezeichnete Wahl für einen Hash-Schlüssel

In der Regel verwenden wir Symbole, um die Schlüssel einer Hash-Tabelle darzustellen:

options = {}
options[:auto_save]     = true
options[:show_comments] = false
Arun Kumar M.
quelle
5

Hier ist ein schöner Benchmark für Zeichenfolgen und Symbole, den ich an der Codecademy gefunden habe:

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."

Die Ausgabe ist:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
Yurii
quelle
2
Lassen Sie uns nicht aus den Augen verlieren, dass dies eine Zehntelsekunde ist.
Casey
Alles ist relativ. Manchmal Hundertstel.
Yurii
2
Eine Hundertstelsekunde über eine Million Iterationen? Wenn das die beste Optimierung ist, die Ihnen zur Verfügung steht, ist Ihr Programm meiner Meinung nach bereits ziemlich gut optimiert.
Casey
0
  • Verwenden Sie Symbole als Hash-Schlüssel-IDs

    {key: "value"}

  • Mit Symbolen können Sie die Methode in einer anderen Reihenfolge aufrufen

     def write (file:, data:, mode: "ascii")
          # der Kürze halber entfernt
     Ende
     Schreiben (Daten: 123, Datei: "test.txt")
  • einfrieren, um als Zeichenfolge zu behalten und Speicherplatz zu sparen

    label = 'My Label'.freeze

Oshan Wisumperuma
quelle