web-dev-qa-db-fra.com

Quand utiliser des symboles au lieu de chaînes dans Ruby?

S'il y a au moins deux occurrences de la même chaîne dans mon script, devrais-je plutôt utiliser un symbole?

88
Alan Coromano

TL; DR

Une règle simple consiste à utiliser des symboles chaque fois que vous avez besoin d'identificateurs internes. Pour Ruby <2.2, n'utilisez les symboles que s'ils ne sont pas générés dynamiquement, afin d'éviter les fuites de mémoire.

Réponse complète

La seule raison de ne pas les utiliser pour les identificateurs générés dynamiquement est pour des raisons de mémoire.

Cette question est très courante car de nombreux langages de programmation ne comportent pas de symboles, mais uniquement des chaînes. Ainsi, les chaînes sont également utilisées comme identificateurs dans votre code. Vous devriez vous préoccuper de ce que sont les symboles censés être _ _ _ _ _ _ _ _ _ _ _ _ _ _ PAS SE PAS DE _ {quand vous devriez utiliser des symboles} _ Les symboles sont censés être des identificateurs. Si vous suivez cette philosophie, les chances sont que vous ferez les choses correctement. 

Il existe plusieurs différences entre la mise en œuvre de symboles et de chaînes. La chose la plus importante à propos des symboles est qu’ils sont immuable. Cela signifie qu'ils n'auront jamais leur valeur modifiée. De ce fait, les symboles sont instanciés plus rapidement que les chaînes et certaines opérations telles que la comparaison de deux symboles sont également plus rapides.

Le fait qu'un symbole soit immuable permet à Ruby d'utiliser le même objet chaque fois que vous faites référence au symbole, ce qui économise de la mémoire. Ainsi, chaque fois que l'interprète lit :my_key, il peut le prendre en mémoire au lieu de l'instancier à nouveau. Cela coûte moins cher que d’initialiser une nouvelle chaîne à chaque fois. 

Vous pouvez obtenir une liste de tous les symboles déjà instanciés avec la commande 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.

Pour les versions de Ruby antérieures à 2.2, une fois qu'un symbole est instancié, cette mémoire ne sera plus jamais libre. Le seul moyen de libérer de la mémoire est de redémarrer l'application. Les symboles sont donc également une cause majeure de fuites de mémoire lorsqu'ils ne sont pas utilisés correctement. Le moyen le plus simple de générer une fuite de mémoire consiste à utiliser la méthode to_sym sur les données d'entrée de l'utilisateur. En effet, ces données étant toujours modifiées, une nouvelle partie de la mémoire sera utilisée indéfiniment dans l'instance logicielle. Ruby 2.2 a introduit le symbole garbage collector , qui libère les symboles générés dynamiquement. Ainsi, les fuites de mémoire générées par la création dynamique de symboles ne constituent plus un problème.

Répondre à votre question:

Est-il vrai que je dois utiliser un symbole au lieu d'une chaîne s'il y a au moins deux chaînes identiques dans mon application ou mon script?

Si vous recherchez un identifiant à utiliser en interne dans votre code, vous devriez utiliser des symboles. Si vous imprimez en sortie, vous devriez utiliser des chaînes, même si elles apparaissent plusieurs fois, même en allouant deux objets différents en mémoire.

Voici le raisonnement:

  1. L'impression des symboles sera plus lente que l'impression des chaînes, car ils sont convertis en chaînes. 
  2. Avoir beaucoup de symboles différents augmentera l'utilisation globale de la mémoire de votre application car ils ne sont jamais désalloués. Et vous n'utilisez jamais toutes les chaînes de votre code en même temps.

Cas d'utilisation par @AlanDert

@AlanDert: si j'utilise plusieurs fois quelque chose comme% input {type:: checkbox} en code haml, que dois-je utiliser comme case à cocher?

Moi oui.

@AlanDert: Mais pour imprimer un symbole sur une page html, il devrait être converti en chaîne, n'est-ce pas? quel est le point de l'utiliser alors?

Quel est le type d'une entrée? Un identifiant du type d'entrée que vous voulez utiliser ou quelque chose que vous voulez montrer à l'utilisateur? 

Il est vrai que ce code deviendra du code HTML à un moment donné, mais au moment où vous écrivez cette ligne de votre code, il s'agit d'un identifiant - il identifie le type de champ de saisie dont vous avez besoin. Ainsi, il est utilisé maintes et maintes fois dans votre code et a toujours la même "chaîne" de caractères que l'identifiant et ne génère pas de fuite de mémoire.

Cela dit, pourquoi ne pas évaluer les données pour voir si les chaînes sont plus rapides?

Voici un repère simple que j'ai créé pour cela:

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

Trois sorties:

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

Donc, utiliser smbols est en réalité un peu plus rapide que d’utiliser des chaînes. Pourquoi donc? Cela dépend de la façon dont HAML est implémenté. J'aurais besoin de pirater un peu le code HAML pour voir, mais si vous continuez à utiliser des symboles dans le concept d'identifiant, votre application sera plus rapide et fiable. Lorsque les questions se posent, comparez-les et obtenez vos réponses.

162
fotanus

En termes simples, un symbole est un nom, composé de caractères, mais immuable. Une chaîne, au contraire, est un conteneur ordonné pour des caractères dont le contenu est autorisé à changer.

13
Boris Stitnicky
  1. Un symbole Ruby est un objet avec une comparaison O(1)

Pour comparer deux chaînes, nous devons éventuellement examiner chaque caractère. Pour deux chaînes de longueur N, cela nécessite des comparaisons N + 1 (que les informaticiens appellent "temps 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"

Mais comme chaque apparence de: foo fait référence au même objet, nous pouvons comparer les symboles en regardant les ID d'objet. Nous pouvons le faire avec une seule comparaison (que les informaticiens appellent "temps O (1)").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Un symbole Ruby est une étiquette dans une énumération de forme libre

En C++, nous pouvons utiliser des "énumérations" pour représenter des familles de constantes liées:

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

Mais comme Ruby est un langage dynamique, nous ne nous inquiétons pas de déclarer un type BugStatus, ni de garder une trace des valeurs légales. Au lieu de cela, nous représentons les valeurs d'énumération sous forme de symboles:

original_status = :open
current_status  = :closed

3. Un symbole Ruby est un nom unique et constant

En Ruby, nous pouvons changer le contenu d'une chaîne:

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

Mais nous ne pouvons pas changer le contenu d'un symbole:

:foo[0]  = ?b # Raises an error
  1. Un symbole Ruby est le mot clé d'un argument de mot clé

Lorsque nous transmettons des arguments de mots clés à une fonction Ruby, nous spécifions les mots clés à l'aide de symboles:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Un symbole Ruby est un excellent choix pour une clé de hachage

En règle générale, nous allons utiliser des symboles pour représenter les clés d'une table de hachage:

options = {}
options[:auto_save]     = true
options[:show_comments] = false
8
Arun Kumar M

Voici un repère de Nice cordes contre symboles que j'ai trouvé à 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."

La sortie est:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
5
Yurii
  • utiliser des symboles comme identificateurs de clé de hachage

    {key: "value"}

  • les symboles vous permettent d'appeler la méthode dans un ordre différent 

      def write (fichier :, données: mode: "ascii") 
 # Supprimé pour des raisons de concision 
 fin
 write (data: 123, fichier: "test.txt") 
  • freeze pour conserver une chaîne et économiser de la mémoire

    label = 'My Label'.freeze

0
Oshanz