Quando usare i simboli invece delle stringhe in Ruby?


98

Se ci sono almeno due istanze della stessa stringa nel mio script, dovrei invece usare un simbolo?

Risposte:


175

TL; DR

Una semplice regola pratica è usare i simboli ogni volta che hai bisogno di identificatori interni. Per Ruby <2.2 usa i simboli solo quando non sono generati dinamicamente, per evitare perdite di memoria.

Risposta completa

L'unico motivo per non utilizzarli per gli identificatori generati dinamicamente è a causa di problemi di memoria.

Questa domanda è molto comune perché molti linguaggi di programmazione non hanno simboli, solo stringhe e quindi le stringhe vengono utilizzate anche come identificatori nel codice. Si dovrebbe essere preoccuparsi di ciò che i simboli sono destinate ad essere , non solo quando si dovrebbe usare simboli . I simboli devono essere identificatori. Se segui questa filosofia, è probabile che farai le cose per bene.

Esistono molte differenze tra l'implementazione di simboli e stringhe. La cosa più importante dei simboli è che sono immutabili . Ciò significa che non cambieranno mai il loro valore. Per questo motivo, i simboli vengono istanziati più velocemente delle stringhe e anche alcune operazioni come il confronto di due simboli sono più veloci.

Il fatto che un simbolo sia immutabile consente a Ruby di utilizzare lo stesso oggetto ogni volta che fai riferimento al simbolo, risparmiando memoria. Quindi ogni volta che l'interprete legge :my_keypuò prenderlo dalla memoria invece di istanziarlo di nuovo. È meno costoso dell'inizializzazione ogni volta di una nuova stringa.

È possibile ottenere un elenco di tutti i simboli già istanziati con il comando 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.

Per le versioni di Ruby precedenti alla 2.2, una volta che un simbolo viene istanziato, questa memoria non sarà mai più libera . L'unico modo per liberare la memoria è riavviare l'applicazione. Quindi i simboli sono anche una delle principali cause di perdite di memoria se utilizzati in modo errato. Il modo più semplice per generare una perdita di memoria è utilizzare il metodo to_symsui dati di input dell'utente, poiché questi dati cambieranno sempre, una nuova parte della memoria verrà utilizzata per sempre nell'istanza software. Ruby 2.2 ha introdotto il simbolo garbage collector , che libera i simboli generati dinamicamente, quindi le perdite di memoria generate dalla creazione di simboli dinamicamente non sono più un problema.

Rispondere alla tua domanda:

È vero che devo usare un simbolo invece di una stringa se ci sono almeno due stringhe uguali nella mia applicazione o script?

Se quello che stai cercando è un identificatore da utilizzare internamente al tuo codice, dovresti usare i simboli. Se stai stampando l'output, dovresti usare le stringhe, anche se appare più di una volta, anche allocando due oggetti diversi in memoria.

Ecco il ragionamento:

  1. La stampa dei simboli sarà più lenta rispetto alla stampa di stringhe perché vengono convertiti in stringhe.
  2. Avere molti simboli diversi aumenterà l'utilizzo complessivo della memoria dell'applicazione poiché non vengono mai deallocati. E non stai mai usando tutte le stringhe del tuo codice allo stesso tempo.

Caso d'uso di @AlanDert

@AlanDert: se uso molte volte qualcosa come% input {type:: checkbox} nel codice haml, cosa dovrei usare come checkbox?

Io si.

@AlanDert: Ma per stampare un simbolo su una pagina html, dovrebbe essere convertito in stringa, no? qual è il punto di usarlo allora?

Qual è il tipo di input? Un identificatore del tipo di input che vuoi usare o qualcosa che vuoi mostrare all'utente?

È vero che a un certo punto diventerà codice HTML, ma nel momento in cui stai scrivendo quella riga del tuo codice, significa essere un identificatore: identifica il tipo di campo di input di cui hai bisogno. Pertanto, viene utilizzato più e più volte nel codice e ha sempre la stessa "stringa" di caratteri dell'identificatore e non genererà una perdita di memoria.

Detto questo, perché non valutiamo i dati per vedere se le stringhe sono più veloci?

Questo è un semplice benchmark che ho creato per questo:

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

Tre uscite:

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

Quindi l'uso di simboli è in realtà un po 'più veloce rispetto all'uso delle stringhe. Perché? Dipende dal modo in cui viene implementato HAML. Avrei bisogno di modificare un po 'il codice HAML per vedere, ma se continui a usare i simboli nel concetto di identificatore, la tua applicazione sarà più veloce e affidabile. Quando le domande colpiscono, confrontale e ottieni le tue risposte.


@andrewcockerham Il collegamento fornito non funziona (Errore-404). Devi rimuovere l'ultimo /(dopo strings) dal collegamento. Eccolo: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ symbols-and-strings
Atul Khanduri

14

In parole povere, un simbolo è un nome, composto da caratteri, ma immutabile. Una stringa, al contrario, è un contenitore ordinato di caratteri, il cui contenuto può cambiare.


4
+1. I simboli e le stringhe sono cose completamente diverse. Non c'è davvero alcuna confusione su quale usare, a meno che non sia stato insegnato male (cioè l'errore "un simbolo è solo una stringa immutabile").
Jörg W Mittag

@ JörgWMittag: Esatto.
Boris Stitnicky

5
hai un punto, tuttavia non rispondere alla domanda che è stata posta. L'OP sta confondendo le stringhe con i simboli, non basta dire che sono cose diverse - dovresti aiutarlo a capire in cosa sono simili e in cosa sono diversi
fotanus

1
@ JörgWMittag che sta accadendo in tutto il web a quanto pare, a meno che non guardi nella documentazione o sei abbastanza fortunato da trovare persone che si preoccupano di spiegare le cose per come sono realmente.
sargas

8
  1. Un simbolo Ruby è un oggetto con confronto O (1)

Per confrontare due stringhe, abbiamo potenzialmente bisogno di guardare ogni carattere. Per due stringhe di lunghezza N, ciò richiederà confronti N + 1 (che gli informatici chiamano "tempo 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"

Ma poiché ogni aspetto di: foo si riferisce allo stesso oggetto, possiamo confrontare i simboli guardando gli ID oggetto. Possiamo farlo con un unico confronto (che gli informatici chiamano "O (1) time").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Un simbolo Ruby è un'etichetta in un'enumerazione in formato libero

In C ++, possiamo usare "enumerazioni" per rappresentare famiglie di costanti correlate:

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

Ma poiché Ruby è un linguaggio dinamico, non ci preoccupiamo di dichiarare un tipo BugStatus o di tenere traccia dei valori legali. Invece, rappresentiamo i valori di enumerazione come simboli:

original_status = :open
current_status  = :closed

3.Un simbolo Ruby è un nome univoco e costante

In Ruby, possiamo cambiare il contenuto di una stringa:

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

Ma non possiamo modificare il contenuto di un simbolo:

:foo[0]  = ?b # Raises an error
  1. Un simbolo Ruby è la parola chiave per un argomento parola chiave

Quando si passano gli argomenti delle parole chiave a una funzione Ruby, si specificano le parole chiave utilizzando i simboli:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Un simbolo Ruby è una scelta eccellente per una chiave hash

In genere, utilizzeremo i simboli per rappresentare le chiavi di una tabella hash:

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

5

Ecco un bel benchmark tra stringhe e simboli che ho trovato alla 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."

L'output è:

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

2
Non perdiamo di vista il fatto che questo è un decimo di secondo.
Casey

È tutto relativo. A volte la centesima materia.
Yurii

2
Un centesimo di secondo su un milione di iterazioni? Se questa è la migliore ottimizzazione disponibile per te, il tuo programma è già abbastanza ben ottimizzato, credo.
Casey

0
  • utilizzare i simboli come identificatori di chiavi hash

    {key: "value"}

  • i simboli consentono di chiamare il metodo in un ordine diverso

     def write (file :, data :, mode: "ascii")
          # rimosso per brevità
     fine
     scrittura (dati: 123, file: "test.txt")
  • congelare per conservare come una stringa e risparmiare memoria

    label = 'My Label'.freeze

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.