Jeśli w moim skrypcie są co najmniej dwa wystąpienia tego samego ciągu, czy powinienem zamiast tego użyć symbolu?
Jeśli w moim skrypcie są co najmniej dwa wystąpienia tego samego ciągu, czy powinienem zamiast tego użyć symbolu?
Odpowiedzi:
Prostą zasadą jest używanie symboli za każdym razem, gdy potrzebujesz identyfikatorów wewnętrznych. W Ruby <2.2 używaj symboli tylko wtedy, gdy nie są generowane dynamicznie, aby uniknąć wycieków pamięci.
Jedynym powodem, dla którego nie należy ich używać do identyfikatorów generowanych dynamicznie, są problemy z pamięcią.
To pytanie jest bardzo częste, 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ę tym, jakie symbole mają być , a nie tylko, kiedy powinieneś ich używać . Symbole mają być identyfikatorami. Jeśli będziesz postępować zgodnie z tą filozofią, 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ą niezmienne . Oznacza to, że ich wartość nigdy nie ulegnie zmianie. Z tego powodu symbole są tworzone szybciej niż łańcuchy, a niektóre operacje, takie jak porównywanie dwóch symboli, są również szybsze.
Fakt, że symbol jest niezmienny, pozwala Ruby na używanie tego samego obiektu za każdym razem, gdy odwołujesz się do symbolu, oszczędzając pamięć. Więc za każdym razem, gdy interpreter czyta :my_key
, może pobrać go z pamięci, zamiast tworzyć instancję ponownie. Jest to mniej kosztowne niż każdorazowa inicjalizacja nowego łańcucha.
Możesz uzyskać listę wszystkich symboli, które zostały już utworzone za pomocą polecenia 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.
W przypadku wersji Ruby starszych niż 2.2, po utworzeniu wystąpienia symbolu pamięć ta nigdy nie będzie już wolna . Jedynym sposobem na zwolnienie pamięci jest ponowne uruchomienie aplikacji. Dlatego symbole są również główną przyczyną wycieków pamięci, jeśli 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 się zmieniają, nowa część pamięci będzie używana na zawsze w wystąpieniu oprogramowania. Ruby 2.2 wprowadził moduł odśmiecania symboli , który uwalnia symbole generowane dynamicznie, więc wycieki pamięci generowane przez dynamiczne tworzenie symboli nie są już problemem.
Odpowiadając na Twoje pytanie:
Czy to prawda, że muszę użyć symbolu zamiast ciągu, jeśli w mojej aplikacji lub skrypcie są co najmniej dwa takie same ciągi?
Jeśli szukasz identyfikatora, który ma być używany wewnętrznie w kodzie, powinieneś używać symboli. Jeśli drukujesz dane wyjściowe, powinieneś używać ciągów, nawet jeśli pojawia się więcej niż raz, nawet przydzielając dwa różne obiekty w pamięci.
Oto powód:
@AlanDert: jeśli często używam czegoś takiego jak% input {type:: checkbox} w kodzie haml, czego powinienem użyć jako pola wyboru?
Ja tak.
@AlanDert: Ale żeby wydrukować symbol na stronie html, należy go przekonwertować na string, prawda? jaki jest sens jej używania?
Jaki jest typ wejścia? Identyfikator typu danych wejściowych, których chcesz użyć, lub coś, co chcesz pokazać użytkownikowi?
To prawda, że w pewnym momencie stanie się kodem HTML, ale w momencie, gdy piszesz ten wiersz kodu, oznacza to, że jest identyfikatorem - identyfikuje, jakiego rodzaju pola wejściowego potrzebujesz. W związku z tym jest używany w kodzie w kółko i ma zawsze ten sam „ciąg” znaków co identyfikator i nie spowoduje wycieku pamięci.
To powiedziawszy, dlaczego nie ocenimy danych, aby sprawdzić, czy ciągi znaków są szybsze?
Oto prosty test porównawczy, który stworzyłem w tym celu:
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
Tak więc używanie smbolów jest w rzeczywistości nieco szybsze niż używanie łańcuchów. Dlaczego? Zależy to od sposobu implementacji HAML. Musiałbym trochę zhakować kod HAML, aby zobaczyć, ale jeśli nadal będziesz używać symboli w koncepcji identyfikatora, twoja aplikacja będzie szybsza i niezawodna. Kiedy pojawią się pytania, porównaj je i uzyskaj odpowiedzi.
Mówiąc prościej, symbol to nazwa złożona ze znaków, ale niezmienna. Wręcz przeciwnie, łańcuch jest uporządkowanym kontenerem na znaki, których zawartość może się zmieniać.
Aby porównać dwa ciągi, potencjalnie musimy przyjrzeć się każdemu znakowi. W przypadku dwóch ciągów o długości N będzie to wymagało porównań 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żde wystąpienie: foo odnosi się do tego samego obiektu, możemy porównać symbole, patrząc na identyfikatory obiektów. Możemy to zrobić za pomocą pojedynczego porównania (które informatycy określają jako „czas O (1)”).
def symbol_comp sym1, sym2
sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
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 lub śledzenie wartości prawnych. Zamiast tego reprezentujemy wartości wyliczenia jako symbole:
original_status = :open
current_status = :closed
3. Symbol Ruby to stała, unikalna nazwa
W Rubim możemy zmienić zawartość łańcucha:
"foo"[0] = ?b # "boo"
Ale nie możemy zmienić zawartości symbolu:
:foo[0] = ?b # Raises an error
Podczas przekazywania argumentów słów kluczowych do funkcji Rubiego określamy słowa kluczowe za pomocą symboli:
# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
:action => 'show',
:id => bug.id
Zazwyczaj używamy symboli do reprezentowania kluczy tablicy skrótów:
options = {}
options[:auto_save] = true
options[:show_comments] = false
Oto dobry test porównawczy ciągów i symboli, 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."
Wynik to:
String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
używaj symboli jako identyfikatorów kluczy hash
{key: "value"}
symbole pozwalają na wywołanie metody w innej kolejności
def write (plik :, dane :, tryb: "ascii") # usunięto dla zwięzłości koniec napisz (dane: 123, plik: "test.txt")
zamrozić, aby zachować jako ciąg i zaoszczędzić pamięć
label = 'My Label'.freeze
/
(postrings
) z linku. Tutaj jest: www.reactive.io/tips/2009/01/11/the-difference-between-ruby- symbols-and-strings