Коли використовувати символи замість рядків у Ruby?


98

Якщо в моєму сценарії є принаймні два екземпляри одного і того ж рядка, чи повинен я замість цього використовувати символ?

Відповіді:


175

TL; DR

Просте правило - використовувати символи кожен раз, коли вам потрібні внутрішні ідентифікатори. Для Ruby <2.2 використовуйте символи лише тоді, коли вони не створюються динамічно, щоб уникнути витоку пам'яті.

Повна відповідь

Єдина причина, щоб не використовувати їх для ідентифікаторів, що генеруються динамічно, - через проблеми з пам'яттю.

Це питання дуже поширене, оскільки багато мов програмування не мають символів, а лише рядки, і таким чином рядки також використовуються як ідентифікатори у вашому коді. Ви повинні турбуватися про те, якими символами призначено бути , а не лише тоді, коли ви повинні використовувати символи . Символи мають на увазі ідентифікатори. Якщо ви будете дотримуватися цієї філософії, шанси на те, що ви зробите все правильно.

Існує кілька відмінностей між реалізацією символів і рядків. Найважливіше в символах - це те, що вони незмінні . Це означає, що у них ніколи не буде змінено їх значення. Через це символи інстанціюються швидше, ніж рядки, а деякі операції, такі як порівняння двох символів, також проходять швидше.

Те, що символ є незмінним, дозволяє Рубі використовувати один і той же об'єкт кожного разу, коли ви посилаєтесь на символ, економлячи пам'ять. Тому кожен раз, коли перекладач читає, :my_keyвін може взяти його з пам'яті, а не інстанціювати його знову. Це менш дорого, ніж щоразу ініціалізувати новий рядок.

Ви можете отримати список усіх символів, які вже створені за допомогою команди 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.

Для версій Ruby до 2.2, коли символ буде створений, ця пам'ять більше ніколи не буде вільною . Єдиний спосіб звільнити пам'ять - це перезапуск програми. Отже символи також є основною причиною витоку пам'яті при неправильному використанні. Найпростішим способом генерування витоку пам'яті є використання методу to_symвведення даних користувача, оскільки ці дані завжди будуть змінюватися, нова частина пам'яті буде назавжди використана в екземплярі програмного забезпечення. Ruby 2.2 представив збірник сміття символів , який звільняє символи, що генеруються динамічно, тому пам'ять, що витікає з динамічного створення символів, вже не викликає занепокоєння.

Відповідь на ваше запитання:

Чи правда, що я повинен використовувати символ замість рядка, якщо в моєму додатку чи сценарії є принаймні дві однакові рядки?

Якщо ви шукаєте ідентифікатор, який потрібно використовувати внутрішньо у вашому коді, вам слід використовувати символи. Якщо ви друкуєте вихід, вам слід рухатись із рядками, навіть якщо вони з’являються не один раз, навіть виділяючи в пам'яті два різних об’єкти.

Ось міркування:

  1. Друк символів буде повільнішим, ніж друк рядків, оскільки вони передані на рядки.
  2. Наявність безлічі різних символів збільшить загальне використання пам’яті вашої програми, оскільки вони ніколи не розміщуються. І ви ніколи не використовуєте одночасно всі рядки зі свого коду.

Використовуйте регістр від @AlanDert

@AlanDert: якщо я використовую багато разів щось подібне% input {type:: checkbox} у коді haml, що я повинен використовувати як прапорець?

Я: Так.

@AlanDert: Але щоб роздрукувати символ на html-сторінці, його слід перетворити на рядок, чи не так? який сенс використовувати його тоді?

Який тип входу? Ідентифікатор типу вводу, який ви хочете використовувати, або щось, що ви хочете показати користувачеві?

Це правда, що він стане кодом HTML в якийсь момент, але в той момент, коли ви пишете цей рядок свого коду, це означає бути ідентифікатором - він визначає, яке поле для введення вам потрібно. Таким чином, він використовується знову і знову у вашому коді, і завжди має той самий "рядок" символів, що ідентифікатор, і не створюватиме витоку пам'яті.

Це означає, чому ми не оцінюємо дані, щоб побачити, чи швидкі струни?

Це простий орієнтир, який я створив для цього:

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

Три виходи:

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

Тож використання smbols насправді трохи швидше, ніж використання рядків. Чому так? Це залежить від способу реалізації HAML. Мені потрібно трохи зламати код HAML, щоб побачити, але якщо ви продовжуєте використовувати символи в концепції ідентифікатора, ваша програма буде швидшою та надійною. Коли питання вражає, порівняйте його та отримайте свої відповіді.


@andrewcockerham Надане посилання не працює (Помилка-404). Ви повинні видалити останнє /(після strings) із посилання. Ось це: www.reactive.io/tips/2009/01/11/the-difference-bet
between-ruby-‌ symbol

14

Простіше кажучи, символ - це ім'я, складене з символів, але незмінне. Рядок, навпаки, - це упорядкований контейнер для символів, вміст яких дозволено змінювати.


4
+1. Символи та рядки - це абсолютно різні речі. Насправді не існує ніякої плутанини щодо того, яким користуватися, якщо тільки вони не навчились погано (тобто помилка "символ - це лише незмінна струна").
Йорг W Міттаг

@ JörgWMittag: Саме так.
Борис Стітнікі

5
Ви маєте точку, однак не відповідайте на поставлене питання. ОП плутає рядки з символами, недостатньо сказати, що це різні речі - ви повинні допомогти йому зрозуміти, чим вони схожі і чим вони відрізняються
fotanus

1
@ JörgWMittag, що відбувається по всьому Інтернету, схоже, якщо ви не заглянете в документацію або не пощастить знайти людей, які піклуються про те, щоб пояснити речі такими, якими вони є насправді.
саргаз

8
  1. Символ Рубі - це об'єкт із порівнянням O (1)

Для порівняння двох рядків нам потенційно потрібно переглянути кожен символ. Для двох рядків довжиною N для цього знадобиться порівняння N + 1 (яке вчені-комп'ютери називають "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"

Але оскільки кожна поява: foo відноситься до одного і того ж об'єкта, ми можемо порівнювати символи, переглядаючи ідентифікатори об'єкта. Ми можемо це зробити за допомогою єдиного порівняння (яке вчені-комп’ютери називають "O (1) time").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Символ Ruby - це мітка у перерахунку у вільній формі

У C ++ ми можемо використовувати "перерахування" для представлення родин споріднених констант:

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

Але оскільки Ruby - це динамічна мова, ми не переживаємо про те, щоб оголосити тип BugStatus або не відслідковувати юридичні значення. Натомість ми представляємо значення перерахування як символи:

original_status = :open
current_status  = :closed

3.А символ Ruby - це постійна унікальна назва

У Ruby ми можемо змінити вміст рядка:

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

Але ми не можемо змінити вміст символу:

:foo[0]  = ?b # Raises an error
  1. Символ Ruby - це ключове слово для аргументу ключового слова

Передаючи аргументи ключових слів функції Ruby, ми визначаємо ключові слова за допомогою символів:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Символ Ruby - відмінний вибір для хеш-ключа

Зазвичай ми будемо використовувати символи для представлення ключів хеш-таблиці:

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

5

Ось хороший орієнтир рядків проти символів, який я знайшов у кодексі:

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

Вихід:

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

2
Не будемо втрачати з уваги той факт, що це десята частина секунди.
Кейсі

Це все відносно. Іноді сота матерія.
Юрій

2
Соту секунди понад мільйон ітерацій? Якщо це найкраща оптимізація, доступна для вас, ваша програма вже досить оптимізована, я думаю.
Кейсі

0
  • використовувати символи як ідентифікатори хеш-ключів

    {key: "value"}

  • символи дозволяють викликати метод в іншому порядку

     def write (файл :, data :, режим: "ascii")
          # видалено для стислості
     кінець
     написати (дані: 123, файл: "test.txt")
  • заморозити, щоб зберегти як рядок і зберегти пам'ять

    label = 'My Label'.freeze

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.