Зачем использовать символы в качестве хеш-ключей в Ruby?
Часто люди используют символы в качестве ключей в хэше Ruby.
В чем преимущество использования строки?
Например:
hash[:name]
против
hash['name']
4 ответа
TL;DR:
Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.
Рубиновые символы неизменны (не могут быть изменены), что значительно упрощает поиск
Краткий (иш) ответ:
Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.
Символы в Ruby в основном являются "неизменяемыми строками".. это означает, что они не могут быть изменены, и это означает, что один и тот же символ при многократном обращении по всему исходному коду всегда сохраняется как одна и та же сущность, например, имеет один и тот же идентификатор объекта,
С другой стороны, строки изменчивы, их можно изменить в любое время. Это означает, что Ruby необходимо хранить каждую строку, которую вы упоминаете в исходном коде, в отдельном объекте, например, если у вас несколько раз упоминается строка "имя" в вашем исходном коде, Ruby необходимо хранить их все в отдельных объектах String, потому что они может измениться позже (такова природа строки Ruby).
Если вы используете строку в качестве ключа Hash, Ruby необходимо оценить строку и посмотреть ее содержимое (и вычислить хеш-функцию для этого) и сравнить результат со значениями (хэшированными) ключей, которые уже хранятся в Hash.,
Если вы используете символ в качестве ключа Hash, это неявно означает, что он неизменен, поэтому Ruby может просто выполнить сравнение (hash function of) object-id с (hashed) object-id ключей, которые уже хранятся в Хеш (намного быстрее)
Недостаток: каждый символ занимает слот в таблице символов интерпретатора Ruby, который никогда не освобождается. Символы никогда не собираются мусором. Так что угловой случай - это когда у вас есть большое количество символов (например, автоматически сгенерированных). В этом случае вы должны оценить, как это влияет на размер вашего интерпретатора Ruby.
Заметки:
Если вы выполняете сравнение строк, Ruby может сравнивать символы только по их идентификаторам объектов, не оценивая их. Это намного быстрее, чем сравнение строк, которые необходимо оценить.
Если вы обращаетесь к хешу, Ruby всегда применяет хеш-функцию для вычисления "хеш-ключа" из любого ключа, который вы используете. Вы можете представить что-то вроде хеша MD5. И тогда Руби сравнивает эти "хешированные ключи" друг с другом.
Длинный ответ:
http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings
http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol
Причина в эффективности, с множественным усилением по отношению к строке:
- Символы являются неизменными, поэтому вопрос "что произойдет, если ключ изменится?" не нужно спрашивать
- Строки дублируются в вашем коде и обычно занимают больше места в памяти.
- Хеш-поиск должен вычислять хеш ключей для их сравнения. Это
O(n)
для строк и константа для символов.
Более того, в Ruby 1.9 введен упрощенный синтаксис только для хеша с символами ключей (например, h.merge(foo: 42, bar: 6)
), а в Ruby 2.0 есть ключевые аргументы, которые работают только для символьных ключей.
Примечания:
1) Вы можете быть удивлены, узнав, что Ruby лечит String
ключи отличается от любого другого типа. В самом деле:
s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash # must be called whenever a key changes!
h[s] # => nil, not "bar"
h.keys
h.keys.first.upcase! # => TypeError: can't modify frozen string
Только для строковых ключей, Ruby будет использовать замороженную копию вместо самого объекта.
2) Буквы "b", "a" и "r" сохраняются только один раз для всех случаев :bar
в программе. До Ruby 2.2 было плохой идеей постоянно создавать новые Symbols
которые никогда не использовались повторно, так как они навсегда останутся в глобальной таблице поиска символов. Ruby 2.2 будет собирать их мусор, так что не беспокойтесь.
3) На самом деле, вычисление хеша для Symbol не заняло никакого времени в Ruby 1.8.x, так как идентификатор объекта использовался напрямую:
:bar.object_id == :bar.hash # => true in Ruby 1.8.7
В Ruby 1.9.x это изменилось, поскольку хэши переходят из одного сеанса в другой (включая Symbols
):
:bar.hash # => some number that will be different next time Ruby 1.9 is ran
Re: в чем преимущество перед использованием строки?
- Стиль: его Рубиновый путь
(Очень) поиск значений немного быстрее, поскольку хеширование символа эквивалентно хешированию целого числа против хеширования строки.
Недостаток: занимает слот в таблице символов программы, который никогда не освобождается.
Я был бы очень заинтересован в продолжении относительно замороженных строк, представленных в Ruby 2.x.
Когда вы имеете дело со множеством строк, поступающих из текстового ввода (я имею в виду HTTP-параметры или полезную нагрузку, например, через Rack), гораздо проще использовать строки везде.
Когда вы имеете дело с десятками из них, но они никогда не меняются (если они "словарный запас" вашего бизнеса), мне нравится думать, что их замораживание может иметь значение. Я еще не делал никаких тестов, но думаю, это будет близко к производительности символов.