Kiedy używać symboli zamiast łańcuchów w Ruby?

Jeśli w moim skrypcie są co najmniej dwie instancje tego samego ciągu znaków, czy powinienem zamiast tego użyć symbolu?

Author: royhowie, 2013-05-18

4 answers

TL;DR

Prostą zasadą jest używanie symboli za każdym razem, gdy potrzebujesz wewnętrznych identyfikatorów. Dla Ruby

Pełna odpowiedź

Jedynym powodem, aby nie używać ich do identyfikatorów generowanych dynamicznie, jest problem z pamięcią.

To pytanie jest bardzo powszechne, ponieważ wiele języków programowania nie ma symboli, tylko ciągi znaków, a zatem ciągi są również używane jako identyfikatory w Twoim kodzie. Powinieneś martwić się o to, jakie symbole mają być , a nie tylko Kiedy powinieneś używać symboli. Symbole mają być identyfikatorami. Jeśli zastosujesz się do tej filozofii, są szanse, że zrobisz wszystko dobrze.

Istnieje kilka różnic między implementacją symboli i łańcuchów. Najważniejszą rzeczą w symbolach jest to, że są one niezmienne. Oznacza to, że ich wartość Nigdy nie ulegnie zmianie. Na oznacza to, że symbole są tworzone szybciej niż ciągi znaków, a niektóre operacje, takie jak porównywanie dwóch symboli, są również szybsze.

Fakt, że symbol jest niezmienny pozwala Rubiemu używać tego samego obiektu za każdym razem, gdy odwołujesz się do symbolu, oszczędzając pamięć. Tak więc za każdym razem, gdy interpreter odczytuje :my_key, może on pobierać go z pamięci zamiast tworzyć go ponownie. Jest to tańsze niż inicjalizacja nowego ciągu za każdym razem.

Możesz uzyskać listę wszystkich symboli, które są już utworzone z polecenie 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.

Dla wersji Rubiego przed 2.2, gdy symbol zostanie utworzony, ta pamięć nigdy nie będzie wolna . Jedynym sposobem na zwolnienie pamięci jest ponowne uruchomienie aplikacji. Tak więc symbole są również główną przyczyną wycieków pamięci, gdy są używane nieprawidłowo. Najprostszym sposobem wygenerowania wycieku pamięci jest użycie metody to_sym na danych wejściowych użytkownika, ponieważ dane te zawsze będą się zmieniać, nowa część pamięci będzie używana na zawsze w instancji oprogramowania. Ruby 2.2 wprowadzono moduł garbage collector symbol , który zwalnia symbole generowane dynamicznie, więc wycieki pamięci generowane przez dynamiczne tworzenie symboli nie są już problemem.

Odpowiedź na twoje pytanie:

Czy to prawda, że muszę używać symbolu zamiast ciągu znaków, jeśli w mojej aplikacji lub skrypcie są co najmniej dwa takie same ciągi znaków?

Jeśli szukasz identyfikatora używanego wewnętrznie w kodzie, powinieneś używać symboli. Jeśli drukujesz dane wyjściowe, powinieneś użyć ciągów, nawet jeśli pojawia się więcej niż jeden raz, nawet przydzielając dwa różne obiekty w pamięci.

Oto uzasadnienie:

  1. Drukowanie symboli będzie wolniejsze niż drukowanie ciągów, ponieważ są one rzucane na ciągi.
  2. posiadanie wielu różnych symboli zwiększy ogólne zużycie pamięci w aplikacji, ponieważ nigdy nie są one dealokowane. I nigdy nie używasz wszystkich ciągów kodu w tym samym czas.

Use case by @ AlanDert

@AlanDert: Jeśli używam wiele razy czegoś w rodzaju %input{type:: checkbox} w kodzie haml, co powinienem użyć jako checkbox?

Ja: Tak.

@AlanDert: ale aby wydrukować symbol na stronie html, należy go przekonwertować na string, prawda? więc po co go używać?

Jaki jest typ wejścia? Identyfikator typu wejścia, którego chcesz użyć lub coś, co chcesz pokazać użytkownikowi?

Prawdą jest, że w pewnym momencie stanie się kodem HTML, ale w momencie pisania tej linii kodu, oznacza to, że jest identyfikatorem-określa jakiego rodzaju pola wejściowego potrzebujesz. Tak więc, jest on używany wielokrotnie w kodzie i ma zawsze taki sam "ciąg" znaków jak identyfikator i nie generuje wycieku pamięci.

To powiedziawszy, dlaczego nie oceniamy danych, aby sprawdzić, czy ciągi są szybsze?

Jest to prosty benchmark, który stworzyłem dla to:

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

Trzy wyjścia:

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

Więc używanie smbolsów jest w rzeczywistości nieco szybsze niż używanie ciągów. Dlaczego? To zależy od sposobu implementacji HAML. Musiałbym trochę zhakować kod HAML, aby zobaczyć, ale jeśli nadal używasz symboli w koncepcji identyfikatora, Twoja aplikacja będzie szybsza i niezawodna. Kiedy uderzają pytania, porównuj je i uzyskaj odpowiedzi.

 154
Author: fotanus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 09:16:58

Mówiąc prościej, symbol jest nazwą, złożoną z znaków, ale niezmienną. String, przeciwnie, jest uporządkowanym kontenerem dla znaków, których zawartość może się zmieniać.

 13
Author: Boris Stitnicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-18 07:17:14
  1. symbol Ruby jest obiektem z porównaniem O(1)

Aby porównać dwa ciągi znaków, potencjalnie musimy przyjrzeć się każdemu znakowi. Dla dwóch ciągów o długości N, będzie to wymagało porównania N+1 (które informatycy nazywają " czasem 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"

Ale ponieważ każdy wygląd :foo odnosi się do tego samego obiektu, możemy porównać symbole patrząc na identyfikatory obiektów. Możemy to zrobić za pomocą jednego porównania (które informatycy nazywają "O (1) czas").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. symbol Ruby jest etykietą w swobodnym wyliczeniu

W C++ możemy użyć" wyliczeń " do reprezentowania rodzin powiązanych stałych:

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

Ale ponieważ Ruby jest językiem dynamicznym, nie martwimy się o deklarowanie typu BugStatus, ani o śledzenie wartości prawnych. Zamiast tego reprezentujemy wartości wyliczania jako symbole:

original_status = :open
current_status  = :closed

3.Symbol Ruby jest stałą, unikalną nazwą

W Rubim możemy zmienić zawartość ciąg znaków:

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

Ale nie możemy zmienić zawartości symbolu:

:foo[0]  = ?b # Raises an error
  1. symbol Ruby jest słowem kluczowym dla argumentu słowa kluczowego

Przekazując argumenty słów kluczowych do funkcji Ruby, określamy słowa kluczowe za pomocą symboli:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. symbol Ruby jest doskonałym wyborem dla klucza hashowego

Zazwyczaj używamy symboli do reprezentowania kluczy tabeli hash:

options = {}
options[:auto_save]     = true
options[:show_comments] = false
 7
Author: Arun Kumar M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-27 15:44:29

Oto fajny benchmark strings vs symbols, który znalazłem w 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."

Wyjście To:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
 5
Author: Yurii Polishchuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-12 10:06:18